2017-04-14 5 views
0

Ich habe dieses Thema in den letzten Tagen recherchiert und habe keine passende Antwort für meine Anwendung gefunden. Ich baue eine GUI in Python 2.7 mit PyQt4, um mit einem Roboter zu interagieren. Ich brauche einen Thread (Haupt), um die mit QT Designer erstellte GUI und einen anderen Thread (Arbeiter) laufen zu lassen, um kontinuierlich Daten zum und vom Roboter zu übertragen. Ich möchte Daten verwenden, die in der GUI (Hauptthread) im unendlichen Worker-Thread eingegeben wurden. In allen Tutorials, die ich gesehen habe, werden Daten nur vom Worker an den Hauptthread gesendet. wie das übliche "Ladebalken" -Beispiel.Python PyQt4 Threading, Variablen teilen

Das ist was ich bisher habe. Dieses Programm importiert die GUI-Ausgabe von QT Designer und druckt Werte auf die Konsole, wenn sie gedrückt werden. Ich habe auch einen unendlichen Thread eingerichtet, der "Hallo" an die Konsole druckt, die funktioniert. Nur um mich in die richtige Richtung zu weisen, können Sie mir zeigen, wie man den unendlichen Ausdruck "Hallo" auf etwas anderes ändert, wenn man auf die Schaltfläche "Drucktaste" klickt? Ich bin vertraut mit C/C++, aber dies ist mein erstes Python-Projekt und jede Hilfe wird geschätzt.

from PyQt4 import QtGui, QtTest, QtCore # Import the PyQt4 module we'll need 
import sys # We need sys so that we can pass argv to QApplication 

import gui # Import the GUI output from QT Designer 


class GUI(QtGui.QMainWindow, gui.Ui_MainWindow): # This class is to interact   with the GUI 
def __init__(self): 
    super(self.__class__, self).__init__() 
    self.setupUi(self) 
    self.threadclass = ThreadClass() 
    self.threadclass.start() # start thread class 

    ######### Binds GUI widgets with functions ##### 
    self.connectbutton.clicked.connect(self.pushbutton) # Call function "pushbutton" when pressed 
    self.motor_up.clicked.connect(self.motorup) # Call function motorup when pressed 
    self.motor_down.clicked.connect(self.motordown) # Call function motorup when pressed 
    self.motor_speed_slider.valueChanged.connect(self.slider) # Call function slider 
    self.auto_manual_check.stateChanged.connect(self.checkbox) # Call function checkbox 
    self.Kp_box.valueChanged.connect(self.kp_entry) 
    self.Ki_box.valueChanged.connect(self.ki_entry) 
    self.Kd_box.valueChanged.connect(self.kd_entry) 


    ######### Functions to run when GUI event is encountered 

def pushbutton(self): # stuff to do if connect button is pressed    ## THIS BUTTON 
    self.connectlabel.setText("Connected") 
    QtTest.QTest.qWait(2000)   
    self.connectlabel.setText("Disconnected") 
    self.emit(SIGNAL("text(Qstring)"),"it worked") 

def slider(self): # Stuff to do when slider is changed 
    print(self.motor_speed_slider.value()) # Print to monitor 

def motorup(self): # Run if motor up is clicked 
    print("motor up") 

def motordown(self): # Run if motor down is clicked 
    print("motor down") 

def checkbox(self): # Run if checkbox is changed 
    if self.auto_manual_check.isChecked(): 
     print("is checked") 
    else: 
     print("unchecked") 

def kp_entry(self): # Run if kp is changed 
    print(self.Kp_box.value()) 

def ki_entry(self): # Run if ki is changed 
    print(self.Ki_box.value()) 

def kd_entry(self):# Run if kd is changed 
    print(self.Kd_box.value()) 



class ThreadClass(QtCore.QThread): # Infinite thread to communicate with robot 
def __init__(self, parent = None): 
    super(ThreadClass, self).__init__(parent) 

def run(self): 
    while 1:   # Runs continuously 
     print "hello" #       CHANGE THIS WHEN pushbutton IS PRESSED 




def main(): # Function to run the main GUI 
app = QtGui.QApplication(sys.argv) # A new instance of QApplication 
form = GUI()     # We set the form to be our ExampleApp 
form.show()       # Show the form 
app.exec_()       # and execute the app 


if __name__ == '__main__':    # if we're running file directly and not importing it 
main()        # run the main function 

EDIT: Vielen Dank für die schnelle und ausführliche Antworten. Nachdem ich mit Ihren Lösungen experimentiert habe, denke ich, dass ich näher komme, aber ich werde sie nicht dazu bringen, für meine Bewerbung zu arbeiten. Ich denke, dass ich in meiner anfänglichen Frage unklar gewesen sein könnte. Ich versuche im Wesentlichen Variablen zwischen Threads zu übergeben, der tatsächliche Druck war nur um zu zeigen, dass die Variable erfolgreich übertragen wurde. Ich denke, dieser abgespeckte Code wird es aufklären.

from PyQt4 import QtGui, QtTest, QtCore # Import the PyQt4 module we'll need 
import sys # We need sys so that we can pass argv to QApplication 

import gui # Import the GUI output from QT Designer 


class GUI(QtGui.QMainWindow, gui.Ui_MainWindow):   # This class is to interact with the GUI 
def __init__(self): 
    super(self.__class__, self).__init__() 
    self.setupUi(self) 
    self.threadclass = ThreadClass() 
    self.threadclass.start() 
    self.Kp_box.valueChanged.connect(self.kp_entry)  # Call function kp_entry if box value is changed 

def kp_entry(self):          # Run if kp is changed     
    print(self.Kp_box.value())       # I WANT TO SEND THE BOX VALUE TO THE WORKER THREAD   

class ThreadClass(QtCore.QThread):       # Infinite thread to communicate with robot 
def __init__(self, parent = None): 
    super(ThreadClass, self).__init__(parent) 

def run(self): 
    while 1:    
     print self.Kp_box.value()      #CONTINUOUSLY PRINT THE UPDATED BOX VALUE  


def main():        # Run the main GUI 
    app = QtGui.QApplication(sys.argv) # A new instance of QApplication 
    form = GUI()      # We set the form to be our ExampleApp 
    form.show()       # Show the form 
    app.exec_()       # and execute the app 

if __name__ == '__main__':    # if we're running file directly and not importing it 
main()        # run the main function 

Alles, was ich tun möchte, ist zu ermöglichen, den Benutzer einen Wert in der GUI zu aktualisieren und diesen Wert dann an den Arbeitsthread übergeben. In diesem Fall habe ich ein Eingabefeld, damit der Benutzer die Nummer ändern kann. Ich brauche dann das Programm, um diese Nummer an den Arbeiter-Thread zu übertragen, der mit dem Roboter kommunizieren wird. Im Moment funktioniert der Code nicht, ich habe es so nah wie möglich gebracht, um besser zu zeigen, was ich versuche zu tun.

Wenn es hilft, das ist meine GUI here

+0

Die Kommentare von [this] (http://stackoverflow.com/a/35534047/1994235) Antwort können nützlich sein (es gibt eine, die erklärt, wie die Signal/Slot-Verbindung um zusätzliche Argumente erweitert werden, so dass sie sind an den Arbeiter geschickt). –

Antwort

0

Es gibt ein paar Möglichkeiten, dies zu tun. Die erste (einfachste) ist eine Codezeile in Ihrer Methode zu setzen pushbutton:

def pushbutton(self): 
    self.threadclass.got_a_click() 

ein entsprechendes Verfahren hinzufügen und Instanzvariable ThreadClass, und die Laufmethode ändern:

def __init__(self): 
    super(ThreadClass, self).__init__(parent) 
    self.flag = False 

def got_a_click(self): 
    self.flag = True 

def run(self): 
    while not self.flag: 
     print "hello" 
    do_something_useful() 

Dies kann sei gut genug. Beachten Sie, dass die Methode got_a_click im Hauptschleifenkontext ausgeführt wird und die run-Methode im sekundären QThread-Kontext ist.

Ein ausgefeilterer Ansatz besteht darin, die Fähigkeit von Qt zu verwenden, Signale an Slots in anderen Threads zu senden. Um dies zu tun, müssen Sie einen QEventLoop im sekundären Thread ausführen. Die QThread-Basisklasse ist dafür bereits eingerichtet: Sie verfügt über eine run-Methode, die nur aus einer Ereignisschleife besteht. Um also eine QThread-Unterklasse zu haben, die eine Ereignisschleife ausführt, überschreiben Sie einfach nicht die run-Methode. Dieser QThread wird einfach nichts tun, bis ein Signal an einen seiner Slots gesendet wird. Wenn das passiert, wird der Slot im sekundären Thread ausgeführt.Der Code sieht wie folgt aus:

class ThreadClass(QtCore.QThread): 
    def __init__(self, parent = None): 
     super(ThreadClass, self).__init__(parent) 
     self.moveToThread(self) 

    def onButtonPress(self): 
     pass # Your code here 

Fügen Sie diese Zeile an den Konstruktor GUI: self.connectbutton.clicked.connect(self.threadclass.onButtonPress)

Jedes Mal, wenn Sie auf die Schaltfläche klicken, onButtonPress läuft im sekundären Thread-Kontext.

Beachten Sie die Anweisung self.moveToThread(self) im ThreadClass-Konstruktor. Dies ist sehr wichtig. Es teilt dem Qt Signal/Slot-Mechanismus mit, dass alle Slots in diesem Objekt durch die Ereignisschleife im ThreadClass-Thread aufgerufen werden sollen. Ohne es würde der Slot onButtonPress im Kontext des Hauptthreads ausgeführt werden, obwohl sich sein Code in ThreadClass befindet.

Eine weitere Falle, auf die Sie achten sollten, ist, dass Sie onButtonPress direkt anrufen. Das wird nicht den Signal/Slot-Mechanismus verwenden, und onButtonPress wird wie jede normale Python-Funktion ausgeführt. Wenn Sie beispielsweise onButtonPress von Ihrer vorhandenen Funktion pushbutton aufgerufen haben, würde sie im Hauptthreadkontext ausgeführt werden. Um das gewünschte Threading-Verhalten zu erhalten, müssen Sie ein Qt-Signal an den onButtonPress Slot senden.

Hier ist die Antwort auf Ihre umformulierte Frage. Das folgende Codefragment funktioniert, wie Sie beabsichtigen:

from PySide import QtGui, QtTest, QtCore # Import the PySide module we'll need 
import sys 

class GUI(QtGui.QMainWindow):   # This class is to interact with the GUI 
    def __init__(self): 
     super(self.__class__, self).__init__() 
     self.Kp_box = QtGui.QSpinBox(self) 
     self.threadclass = ThreadClass(self) 
     self.threadclass.start() 
     self.Kp_box.valueChanged.connect(self.kp_entry)  # Call function kp_entry if box value is changed 

    def kp_entry(self):          # Run if kp is changed     
     print("Main", self.Kp_box.value())       # I WANT TO SEND THE BOX VALUE TO THE WORKER THREAD   

class ThreadClass(QtCore.QThread):       # Infinite thread to communicate with robot 
    def __init__(self, main_window): 
     self.main_window = main_window 
     super(ThreadClass, self).__init__(main_window) 

    def run(self): 
     while 1:    
      print(self.main_window.Kp_box.value())      #CONTINUOUSLY PRINT THE UPDATED BOX VALUE  


def main():        # Run the main GUI 
    app = QtGui.QApplication(sys.argv) # A new instance of QApplication 
    form = GUI()      # We set the form to be our ExampleApp 
    form.show()       # Show the form 
    app.exec_()       # and execute the app 

if __name__ == '__main__':    # if we're running file directly and not importing it 
    main()        # run the main function 

Was ich aus dem Code geändert:

  1. ich pyside nicht PyQt verwenden, aber sie sind meist kompatibel (oder zumindest sie sollen Sein). Ich musste den ersten Import ändern Anweisung; Sie müssen es zurück ändern.
  2. Um die Abhängigkeit vom GUI-Tool zu entfernen, ersetzte ich Ihr Hauptfenster durch ein Fenster mit einer einzelnen SpinBox. Das zeigt die wesentlichen Funktionen Ihres Multithreading-Problems und entkoppelt es von Ihren anderen Anwendungsanforderungen.
  3. Ihr sekundärer Thread konnte nicht auf die SpinBox zugreifen, da keine Variable vorhanden ist, die darauf verweist. Ich vermute das ist das große Problem das du hast. Es wird behoben, indem das Hauptfensterobjekt an den ThreadClass-Konstruktor übergeben und in einer Instanzvariable gespeichert wird.

In keiner Hinsicht wird etwas von einem Thread zum anderen übertragen. Dieser Code verwendet einfach die Tatsache, dass alle Variablen in einem Python-Programm in demselben Speicherbereich leben, selbst wenn das Programm Multithread ist. [Das wäre nicht wahr, wenn das Programm mehrere Prozesse verwendet.]

Dieses kleine Programm bietet keine Möglichkeit zum Schließen anmutig.

+0

Danke Paul für deinen tollen Post! Ich habe mit Ihrer Lösung experimentiert und hatte nur begrenzten Erfolg. Ich war in der Lage, dem Worker-Thread zu sagen, dass er eine Druckmethode ausführen soll, wenn die GUI-Schaltfläche gedrückt wird, aber keine Variablen. Ich habe meine erste Antwort bearbeitet, um mein Ziel besser zu veranschaulichen. –

+0

Überarbeitung meiner Antwort im Licht Ihrer überarbeiteten Frage :-) –