繁体   English   中英

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

[英]PyQt5 QObject: Cannot create children for a parent that is in a different thread

我正在使用PyQt5在菜单系统托盘中工作。 我对PyQt5非常陌生,我想做的是在不阻止菜单(多线程)的情况下触发操作。 在许多地方阅读完之后,我得出的结论是使用Qthread应该是Qthread的方法(但是,如果我能理解该类的工作原理……)。 但是,考虑到我的应用程序非常简单,使用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.

此后,菜单仍然具有“响应性”,但不能触发更多操作,因此它仍然是“功能性”的。 此外,似乎没有更多的线程被创建。 如果有人可以解释我为什么会发生这种情况,我将感到非常高兴。 我看不到光...

更新

我现在创建了一个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的外部类中恢复availablecc 但是由于某些原因, menubar类未收到该消息。 我正在基于此https://stackoverflow.com/a/33453124/1995261

我将继续回答自己。 https://stackoverflow.com/a/33453124/1995261的启发,我通过实现以下方法解决了这一问题:

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时, threads数保持正确。

希望对大家有帮助。 有关更精确的信息(我仍在学习PyQt,并且措辞可能不太好),建议您检查上面发布的链接。

由于这是该错误的Google最佳答案,并且花费了我比预期的时间才能正确解决此问题,因此,我将分享我针对Python 3和PyQt 5的非常简单的解决方案(如果您更改某些导入,它也应该在PyQt4中工作) )。

我遇到的情况是带有右键单击菜单的系统托盘图标,当其他线程请求它时应重新构建该图标。 您当然可以将其应用于要通过线程限制进行通信的其他问题。

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()

基本上,您必须创建一个新class MySignal(QtCore.QObject) why 我创建了一个包含两个示例的类-一个示例不发送任何参数,而另一个示例则可以传递字符串。 您当然可以定义其他参数 然后,在目标线程中,创建该类的新实例,并将该类中的函数连接到目标内部的函数(本例中为systray图标)。 在那之后,您现在可以像在while循环中一样调用emit(...)函数。
现在,Qt很高兴,因为与直接从其他线程调用trayIcon.build_menu()相比,您只发出一个信号即可。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM