2016-04-06 18 views
1

Ich arbeite in einer Menü-Taskleiste mit PyQt5. Ich bin sehr neu mit PyQt5, und was ich tun möchte, ist eine Aktion ohne das Menü zu blockieren (Multithreading). Nachdem ich an vielen Stellen gelesen habe, bin ich zu dem Schluss gekommen, dass die Verwendung von Qthread der Weg sein sollte (aber wenn ich nur verstehen könnte, wie diese Klasse funktioniert ...). Allerdings wäre die Verwendung von threading nicht so schlecht, da meine Anwendung sehr einfach ist. So habe ich den folgenden Code versucht import threading mit:PyQt5 QObject: Ich kann keine untergeordneten Elemente für ein übergeordnetes Objekt in einem anderen Thread erstellen

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

Wenn ich mein Menü starten, ist alles an Ort und Stelle, wie ich es erwartet. Wenn ich dann auf das Menü Search klicke, löst die Aktion die Methode self.search_cast aus, und mein Menü wird mit der Liste gefüllt, die es findet. Ich kann auch ohne meine Anwendung macht die Suche sehen blockiert zu werden, aber wenn es fertig ist ich folgende Fehlermeldungen erhalten:

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. 

Danach ist das Menü noch „funktional“ in dem Sinne, dass es reagiert aber nicht mehr Aktion kann ausgelöst werden. Darüber hinaus scheint es, dass keine weiteren Threads erstellt werden. Ich wäre froh, wenn mir jemand erklären könnte, warum das so ist? Ich sehe nicht, das Licht ...

aktualisieren:

ich geschaffen habe jetzt ein worker.py das enthält:

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

Dann habe ich in den class menubar folgenden hinzugefügt:

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. 

Und jetzt bekomme ich den folgenden Fehler:

AttributeError: 'casting' object has no attribute 'availablecc' 

Ich stelle sicher, dass tatsächlich die workeravailablecc von einer externen Klasse erholt, dass ich cc genannt. Aber aus irgendeinem Grund wird nicht von der menubar Klasse empfangen. Ich arbeite basierend auf diesem https://stackoverflow.com/a/33453124/1995261

Antwort

1

Ich werde fortfahren, mich selbst zu beantworten. Inspiriert von https://stackoverflow.com/a/33453124/1995261, löste ich dies durch die Umsetzung der folgenden:

1) Ich erstellte eine worker.py, die die Methode _search_cast_, die das Menü blockiert ausgeführt wurde. Wenn diese Methode die Suche beendet hat, gibt sie zwei Signale aus: a) eine Information, dass er die list wiederhergestellt hat und b) dass die Methode beendet ist.

#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 der main.py abgeladen ich folgende und ich versuche, in den Code mit Kommentaren zu erklären:

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

Auf diese Weise, wenn die list das Menü nicht blockiert werden Benutzer, Es werden keine Fehler auf dem Bildschirm angezeigt und die Nummer threads, wenn sie in activity monitor überwacht werden, bleibt korrekt.

Ich hoffe, das hilft den Menschen. Für genauere Informationen (ich lerne immer noch PyQt und meine Formulierung ist möglicherweise nicht sehr gut), empfehle ich Ihnen, den Link, den ich oben gepostet habe, zu überprüfen.

+0

Hey vielen Dank für die Freigabe des Codes. Ich habe versucht, das zu tun, was du gesagt hast, aber jetzt reagiert meine GUI nicht mehr, während meine verbundene Methode (in deinem Fall onIntReady) auf der Konsole funktioniert (weil ich sie für die Konsole geschrieben habe) Hast du eine Idee? – Hilal

0

Da dies die Google Top-Antwort für diesen Fehler ist und ich länger als erwartet brauchte, um dies richtig auszuarbeiten, werde ich meine sehr einfache Lösung für Python 3 und PyQt 5 teilen (wenn Sie einige Importe ändern, sollte es funktionieren PyQt4 auch ich denke).

Die Situation, die ich hatte, war ein Systray-Icon mit einem Rechtsklick-Menü, das neu erstellt werden sollte, wenn ein anderer Thread es anfordert. Sie können dies natürlich auch auf andere Probleme anwenden, bei denen Sie über Threadgrenzen kommunizieren möchten.

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

Grundsätzlich müssen Sie einen neuen class MySignal(QtCore.QObject)why erstellen. Ich habe eine Klasse mit zwei Beispielen erstellt - eines, das keine Argumente an ein anderes sendet, an das Sie eine Zeichenfolge übergeben können. Sie können natürlich define other arguments. Dann erstellen Sie in Ihrem Ziel-Thread eine neue Instanz dieser Klasse und verbinden die Funktionen dieser Klasse mit den Funktionen in Ihrem Ziel (in meinem Fall das Systray-Symbol). Danach können Sie jetzt die emit(...) Funktionen wie in der while-Schleife aufrufen.
Jetzt Qt ist glücklich, wie Sie nur ein Signal im Vergleich zu, wenn Sie trayIcon.build_menu() direkt aus einem anderen Thread aufrufen.

Verwandte Themen