2017-04-14 2 views
0

J'ai fait des recherches sur ce sujet au cours des derniers jours et je n'ai pas trouvé de réponse adaptée à mon application. Je construis une interface graphique en python 2.7 avec PyQt4 pour interagir avec un robot. J'ai besoin d'un thread (principal) pour exécuter l'interface graphique créée avec QT Designer et un autre thread (worker) pour transmettre en continu des données vers et depuis le robot. Je veux utiliser les données saisies dans l'interface graphique (thread principal) dans le thread de travail infini. Dans tous les didacticiels que j'ai vus, les données sont uniquement envoyées par le worker au thread principal; comme l'exemple habituel de "barre de chargement".Python PyQt4 Filetage, variables de partage

C'est ce que j'ai jusqu'à présent. Ce programme importe la sortie de l'interface graphique de QT Designer et imprime les valeurs sur la console quand elles sont pressées. J'ai aussi un fil infini mis en place qui imprime "bonjour" à la console qui fonctionne. Juste pour me faire pointer dans la bonne direction, pouvez-vous me montrer comment changer l'impression infinie "bonjour" à quelque chose d'autre quand le bouton appelé "bouton-poussoir" est cliqué? Je suis familier avec le C/C++ mais c'est mon premier projet Python et toute aide est appréciée.

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: Merci pour les réponses rapides et détaillées. Après avoir expérimenté vos solutions, je pense que je me rapproche, mais je ne les fais pas travailler pour mon application, je pense que je n'ai pas été clair dans ma question initiale. Je tente essentiellement de passer des variables entre les threads, l'impression réelle était juste pour montrer que la variable a été transférée avec succès. Je pense que ce code dépouillé l'éclaircira. Tout ce que je veux faire est de permettre à l'utilisateur de mettre à jour une valeur dans l'interface graphique, puis de passer cette valeur au thread de travail. Dans ce cas, j'ai une boîte de saisie pour que l'utilisateur puisse changer le numéro. J'ai alors besoin du programme pour transférer ce nombre au thread de travail qui communiquera avec le robot. À l'heure actuelle, le code ne fonctionne pas, je l'ai juste obtenu le plus près possible pour mieux montrer ce que j'essaie de faire.

Si elle aide, ceci est mon GUI here

+0

Les commentaires de [this] (http://stackoverflow.com/a/35534047/1994235) answer peuvent être utiles (il y en a un qui explique comment étendre la connexion signal/slot pour inclure des arguments supplémentaires afin qu'ils soient envoyé au travailleur). –

Répondre

0

Il y a deux façons de le faire. La première (simple) on est de mettre une ligne de code dans votre méthode pushbutton:

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

Ajouter une méthode correspondante et variable d'instance à ThreadClass, et changer la méthode d'exécution:

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

Cela peut sois assez bon. Notez que la méthode got_a_click s'exécute dans le contexte de la boucle principale et la méthode run dans le contexte secondaire QThread.

Une approche plus sophistiquée consiste à utiliser la capacité de Qt à envoyer des signaux aux slots dans d'autres threads. Pour ce faire, vous devez exécuter un QEventLoop dans le thread secondaire. La classe de base QThread est déjà configurée pour cela: elle a une méthode run qui consiste simplement en une boucle d'événement. Donc, pour avoir une sous-classe QThread qui exécute une boucle d'événements, ne surchargez pas simplement la méthode d'exécution. Ce QThread restera là à ne rien faire jusqu'à ce qu'un signal soit envoyé à l'un de ses slots. Lorsque cela se produit, le Slot s'exécute dans le thread secondaire.Le code ressemble à ceci:

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

    def onButtonPress(self): 
     pass # Your code here 

Ajouter cette ligne au constructeur de GUI: self.connectbutton.clicked.connect(self.threadclass.onButtonPress)

Chaque fois que vous cliquez sur le bouton, onButtonPress exécute dans le contexte de thread secondaire.

Notez l'instruction self.moveToThread(self) dans le constructeur ThreadClass. C'est très important. Il indique au mécanisme Qt Signal/Slot que tous les slots de cet objet doivent être appelés via la boucle d'événements dans le thread ThreadClass. Sans cela, l'emplacement onButtonPress s'exécuterait dans le contexte du thread principal, même si son code se trouve dans ThreadClass.

Un autre écueil à surveiller est d'appeler directement onButtonPress. Cela n'utilisera pas le mécanisme Signal/Slot, et onButtonPress s'exécutera comme n'importe quelle fonction Python ordinaire. Par exemple, si vous appelez onButtonPress à partir de votre fonction existante pushbutton, il s'exécutera dans le contexte de thread principal. Pour obtenir le comportement de thread souhaité, vous devez émettre un signal Qt dans la fente onButtonPress.

Voici la réponse à votre question reformulée. Le fragment de code suivant fonctionne comme vous le souhaitez:

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 

Ce que je changé de votre code:

  1. J'utilise PySide pas PyQt mais ils sont pour la plupart compatibles (ou au moins qu'ils sont censés être). J'ai dû changer la première déclaration d'importation ; vous devrez le changer.
  2. Pour supprimer la dépendance sur l'outil de l'interface graphique, j'ai remplacé votre fenêtre principale par celle qui a une seule SpinBox. Cela démontre les caractéristiques essentielles de votre problème de multithreading et découplé des autres exigences de votre application.
  3. Votre thread secondaire n'a pas pu accéder à SpinBox car il n'a aucune variable qui le référence. Je pense que c'est le gros problème que vous avez. Il est corrigé en transmettant l'objet window principal au constructeur ThreadClass et en le stockant dans une variable d'instance.

En aucun sens, rien n'est transféré d'un thread à un autre. Ce code utilise simplement le fait que toutes les variables d'un programme Python vivent dans le même espace mémoire, même si le programme est multithread. [Ce ne serait pas le cas si le programme utilisait plusieurs processus .]

Ce petit programme ne permet pas de fermer correctement.

+0

Merci Paul pour votre excellent post! J'ai expérimenté avec votre solution et j'ai eu un succès limité. J'ai été en mesure d'indiquer au thread de travail d'exécuter une méthode d'impression lorsque le bouton GUI est enfoncé, mais pas de partager des variables. J'ai modifié ma réponse initiale pour mieux illustrer mon objectif. –

+0

A retravaillé ma réponse à la lumière de ta question retravaillée :-) –