2016-04-06 117 views
1

我正在使用PyQt5在菜單系統托盤中工作。我對PyQt5非常新,我想要做的是觸發一個沒有阻塞菜單的操作(多線程)。在讀過很多地方之後,我得出結論認爲使用Qthread應該是一條路(但只要我能理解該課程是如何工作的......)。但是,使用threading不會那麼糟糕,要麼因爲我的應用程序非常簡單。所以,我曾嘗試使用import threading下面的代碼:PyQt5 QObject:無法爲不同線程的父項創建子項

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

回答

1

我會繼續回答自己。受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

我希望這可以幫助人們。爲了獲得更準確的信息(我仍在學習PyQt,我的措辭可能不太好),我建議你檢查我上面發佈的鏈接。

+0

嘿感謝的共享代碼。我試圖做你說什麼,但現在我的GUI沒有響應,而我的連接方法(在你的情況onIntReady)正在控制檯。(因爲我寫的控制檯)你有什麼想法? – Hilal

0

由於這是此錯誤的谷歌頂部的答案,我花了比預期更長的正常工作了這一點,我將分享我的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。然後在你的目標線程中創建這個類的一個新實例,並將該類的函數連接到目標中的函數(我的例子中的系統托盤圖標)。之後,您現在可以像在循環中那樣調用emit(...)函數。
現在Qt是高興,因爲你只是相比時,你會直接調用trayIcon.build_menu()從不同的線程發出信號。

相關問題