I have been researching this topic for the past few days and have not found a suitable answer for my application. I am building a GUI in python 2.7 with PyQt4 to interact with a robot. I need one thread(main) to run the GUI created with QT Designer and another thread(worker) to continuously transmit data to and from the robot. I want to use data entered in the GUI(main thread) in the infinite worker thread. In all of the tutorials I have seen, data is only sent from the worker to the main thread; like the usual "loading bar" example.
This is what I have so far. This program imports the GUI output from QT Designer and prints values to the console when they are pressed. I also have an infinite thread set up that prints "hello" to the console which does work. Just to get me pointed in the right direction, can you show me how to change the infinite print "hello" to something else when the the button called "pushbutton" is clicked? I am familiar with C/C++ but this is my first Python project and any help is appreciated.
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: Thank you for the quick and detailed responses. After experimenting with your solutions I think I'm getting closer, however I'm not getting them to work for my application, I think I may have been unclear in my initial question. I am essentially trying to pass variables between threads, the actual printing was just to show that the variable was successfully transferred. I think this stripped down code will clear it up.
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
All I want to do is enable the user to update a value in the GUI and then pass that value to the worker thread. In this case, I have an entry box so the user can change the number. I then need the program to transfer that number to the worker thread which will communicate with the robot. Right now the code does not work, I just got it as close as I could to better show what I'm trying to do.
If it helps, this is my GUI here
There are a couple of ways to do this. The first (simplest) one is to put a line of code in your method pushbutton
:
def pushbutton(self):
self.threadclass.got_a_click()
Add a corresponding method and instance variable to ThreadClass, and change the run method:
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()
This may be good enough. Note that the method got_a_click
runs in the main loop context, and the run
method is in the secondary QThread
context.
A more sophisticated approach is to use Qt's ability to send Signals to Slots in other threads. In order to do that, you must run a QEventLoop in the secondary thread. The QThread base class is already set up for this: it has a run
method that consists of just an event loop. So in order to have a QThread subclass that runs an event loop, simply don't override the run method. This QThread will just sit there doing nothing until a Signal gets sent to one of its Slots. When that happens the Slot will execute in the secondary thread. The code looks like this:
class ThreadClass(QtCore.QThread):
def __init__(self, parent = None):
super(ThreadClass, self).__init__(parent)
self.moveToThread(self)
def onButtonPress(self):
pass # Your code here
Add this line to the constructor of GUI
: self.connectbutton.clicked.connect(self.threadclass.onButtonPress)
Each time you click the button, onButtonPress
runs in the secondary thread context.
Note the statement self.moveToThread(self)
in the ThreadClass constructor. This is very important. It tells the Qt Signal/Slot mechanism that all the Slots in this object are to be invoked through the event loop in the ThreadClass thread. Without it, the Slot onButtonPress
would run in the context of the main thread, even though its code is located in ThreadClass
.
Another pitfall to watch out for is calling onButtonPress
directly. That will not use the Signal/Slot mechanism, and onButtonPress will execute like any ordinary Python function. For example, if you called onButtonPress
from your existing function pushbutton
it would run in the main thread context. In order to get the threading behavior you desire, you must cause a Qt Signal to be emitted to the onButtonPress Slot.
Here is the answer to your re-phrased question. The following code fragment works as you intend:
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
What I changed from your code:
In no sense is anything being transferred from one thread to another. This code simply uses the fact that all the variables in a Python program live in the same memory space, even if the program is multi-threaded. [That wouldn't be true if the program used multiple processes .]
This little program doesn't provide any way of closing gracefully.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.