【问题标题】:PyQt5 QObject: Cannot create children for a parent that is in a different threadPyQt5 QObject:无法为不同线程中的父级创建子级
【发布时间】:2016-07-26 23:51:04
【问题描述】:

我正在使用 PyQt5 在菜单系统托盘中工作。我对 PyQt5 很陌生,我想做的是在不阻塞菜单的情况下触发一个动作(多线程)。在阅读了很多地方之后,我得出的结论是使用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 工作

【问题讨论】:

    标签: python multithreading pyqt5


    【解决方案1】:

    由于这是此错误的 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。我创建了一个包含两个示例的类 - 一个不发送任何参数,另一个可以传递字符串。你当然可以define other arguments。然后在您的目标线程中创建此类的新实例并将该类中的函数连接到目标内的函数(在我的情况下为系统托盘图标)。之后,您现在可以像在 while 循环中那样调用 emit(...) 函数。
    现在 Qt 很高兴,因为与直接从不同线程调用 trayIcon.build_menu() 相比,您只需发出一个信号。

    【讨论】:

    • 备注:在这种情况下,信号可以从任何线程emitted,但connects信号给函数的主线程将执行回调函数。请参考Signals and Slots Across Threads(和/或another answer)来确定回调函数在哪个线程中执行。
    【解决方案2】:

    我会继续回答自己。受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 中,我转储了以下内容,并尝试在代码中使用 cmets 进行解释:

    #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时,菜单不会被阻塞,屏幕上不会显示错误,threadsactivity monitor中监控时的数量保持正确。

    我希望这对人们有所帮助。如需更准确的信息(我还在学习 PyQt,我的措辞可能不是很好),建议您查看我在上面发布的链接。

    【讨论】:

    • 您好,感谢您分享代码。我试着按照你说的做,但现在我的 gui 没有响应,而我的连接方法(在你的情况下为 onIntReady)正在控制台上运行。(因为我为控制台编写了它)你有什么想法吗?
    猜你喜欢
    • 1970-01-01
    • 2011-03-17
    • 1970-01-01
    • 1970-01-01
    • 2012-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多