PyQt4 Wait in thread for user input from GUI
By default, a QThread
has an event loop that can process signals and slots. In your current implementation, you have unfortunately removed this behaviour by overriding QThread.run
. If you restore it, you can get the behaviour you desire.
So if you can't override QThread.run()
, how do you do threading in Qt? An alternative approach to threading is to put your code in a subclass of QObject
and move that object to a standard QThread
instance. You can then connect signals and slots together between the main thread and the QThread
to communicate in both directions. This will allow you to implement your desired behaviour.
In the example below, I've started a worker thread which prints to the terminal, waits 2 seconds, prints again and then waits for user input. When the button is clicked, a second separate function in the worker thread runs, and prints to the terminal in the same pattern as the first time. Please note the order in which I use moveToThread()
and connect the signals (as per this).
Code:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import time
class MyWorker(QObject):
wait_for_input = pyqtSignal()
done = pyqtSignal()
@pyqtSlot()
def firstWork(self):
print 'doing first work'
time.sleep(2)
print 'first work done'
self.wait_for_input.emit()
@pyqtSlot()
def secondWork(self):
print 'doing second work'
time.sleep(2)
print 'second work done'
self.done.emit()
class Window(QWidget):
def __init__(self, parent = None):
super(Window, self).__init__()
self.initUi()
self.setupThread()
def initUi(self):
layout = QVBoxLayout()
self.button = QPushButton('User input')
self.button.setEnabled(False)
layout.addWidget(self.button)
self.setLayout(layout)
self.show()
@pyqtSlot()
def enableButton(self):
self.button.setEnabled(True)
@pyqtSlot()
def done(self):
self.button.setEnabled(False)
def setupThread(self):
self.thread = QThread()
self.worker = MyWorker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.firstWork)
self.button.clicked.connect(self.worker.secondWork)
self.worker.wait_for_input.connect(self.enableButton)
self.worker.done.connect(self.done)
# Start thread
self.thread.start()
if __name__ == "__main__":
app = QApplication([])
w = Window()
app.exec_()
Make the worker thread wait for user input in GUI thread? Python/PyQt
The easiest way to communicate between threads is with the Queue module. Have the GUI thread put an "okay" value and have the worker do a blocking get to receive the okay signal.
PyQt4: How to pause a Thread until a signal is emitted?
You can't display a QDialog
from within a QThread
. All GUI related stuff must be done in the GUI thread (the one that created the QApplication
object). What you could do is to use 2 QThread
:
- 1st: perform phase1. You can connect the
finished
signal of thisQThread
to a slot in theQMainWindow
that will display the popup (usingQDialog.exec_()
so it will be modal). - 2nd: perform phase2. You create the
QThread
after the popup shown here above has been closed.
Python PyQt4 Threading, sharing variables
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:
- I use PySide not PyQt but they're mostly compatible (or at least
they're supposed to be). I had to change the first import
statement; you will have to change it back. - To remove the dependency on the GUI tool, I replaced your main
window with one that has a single SpinBox. That demonstrates the
essential features of your multithreading problem and decouples it
from your other application requirements. - Your secondary thread could not access the SpinBox because it has no
variable that references it. I suspect that's the big problem you're
having. It's fixed by passing the main window object to the
ThreadClass constructor and storing it in an instance variable.
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.
Pause worker thread and wait for event from main thread
Below is a simple demo based on the code in your question which does what you want. There is not much to say about it, really, other than that you need to communicate between the worker and the main thread via signals (in both directions). The finished
signal is used to quit the thread, which will stop the warning message QThread: "Destroyed while thread is still running"
being shown.
The reason why you are seeing the error:
TypeError: connect() slot argument should be a callable or a signal, not `NoneType'
is because you are trying to connect a signal with the return value of a function (which is None
), rather than the function object itself. You must always pass a python callable object to the connect
method - anything else will raise a TypeError
.
Please run the script below and confirm that it works as expected. Hopefully it should be easy to see how to adapt it to work with your real code.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Cont(QWidget):
confirmed = pyqtSignal()
def __init__(self):
super(Cont, self).__init__()
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.worker.bv.connect(self.bv_test)
self.worker.finished.connect(self.thread.quit)
self.confirmed.connect(self.worker.process_two)
self.thread.started.connect(self.worker.process_one)
self.thread.start()
def bv_test(self, n):
k = QMessageBox(self)
k.setAttribute(Qt.WA_DeleteOnClose)
k.setText('Quantity: {}'.format(n))
k.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
if k.exec_() == QMessageBox.Yes:
self.confirmed.emit()
else:
self.thread.quit()
class Worker(QObject):
bv = pyqtSignal(int)
finished = pyqtSignal()
def process_two(self):
print('process: two: started')
QThread.sleep(1)
print('process: two: finished')
self.finished.emit()
def process_one(self):
print('process: one: started')
QThread.sleep(1)
self.bv.emit(99)
print('process: one: finished')
app = QApplication([''])
win = Cont()
win.setGeometry(100, 100, 100, 100)
win.show()
app.exec_()
How to wait for user input in PyQt with Line Edit?
Structured programming has a different paradigm than event-oriented programming, GUIs use events to warn the slots that should perform that task.
In your case, the processing part must be done in another method and called when a signal is issued
For your case the QLineEdit
widget has 2 signals that could serve you, the first one is editingFinished
, and the second is returnPressed
, in this case I choose the second that is emitted when I press the enter or return key. Then we connect that signal with the called process slot, that executes the task.
I made some changes that do not affect the design, first change the base class from QDialog to QWidget, in addition to the style of connection between the signals and the slots. If you want to close the window you must use close()
complete code:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
class myWidget(QWidget):
def __init__(self,parent=None):
super(myWidget, self).__init__(parent)
self.lineEdit = QLineEdit()
self.textBrowser = QTextBrowser()
self.top_btn = QPushButton("Ask me", )
self.bottom_btn = QPushButton("disable")
layout = QVBoxLayout()
layout.addWidget(self.textBrowser)
layout.addWidget(self.lineEdit)
layout.addWidget(self.top_btn)
layout.addWidget(self.bottom_btn)
self.setLayout(layout)
self.lineEdit.setDisabled(True)
self.top_btn.clicked.connect(self.inputFunc)
self.lineEdit.returnPressed.connect(self.process)
#self.bottom_btn.clicked.connect(self.disableLine)
def inputFunc(self):
self.lineEdit.setDisabled(False)
self.textBrowser.setText("Welcome to #1 button. what do you want to do?")
def process(self):
userInput = self.lineEdit.text()
if userInput == "anything":
self.textBrowser.append("Ok i will leave you alone")
#self.close()
else:
self.textBrowser.append("say what?")
self.lineEdit.clear()
app = QApplication(sys.argv)
w = myWidget()
w.show()
sys.exit(app.exec_())
Pass data to thread from GUI on runtime in python
It is not necessary that the WebServer live in another thread, it is only necessary that the eventloop be executed in another thread. In this case, the WebServer exchanges the data with the server thread through a queue. Although the QObjects are not thread-safe, the signals are so, there is no problem in emitting the signal from a thread other than the one that the QObject lives
import asyncio
import threading
import queue
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets
class WebServer(QtCore.QObject):
dataReady = QtCore.pyqtSignal(object)
def startServer(self):
self.m_loop = asyncio.new_event_loop()
self.m_queue = queue.Queue()
asyncio.set_event_loop(self.m_loop)
coro = asyncio.start_server(
self.handle_update, "127.0.0.1", 10000, loop=self.m_loop
)
self.server = self.m_loop.run_until_complete(coro)
print("Serving on {}".format(self.server.sockets[0].getsockname()))
threading.Thread(target=self.m_loop.run_forever, daemon=True).start()
@QtCore.pyqtSlot(object)
def setData(self, data):
if hasattr(self, "m_queue"):
self.m_queue.put(data)
def stop(self):
if hasattr(self, "m_loop"):
self.m_loop.stop()
async def handle_update(self, reader, writer):
reply = ""
data = await reader.read(100)
message = data.decode()
addr = writer.get_extra_info("peername")
print(f"Received: {message} from {addr}")
if not self.m_queue.empty():
data = self.m_queue.get(block=False)
reply = data
print(f"Send: {reply}")
writer.write(str(reply).encode())
await writer.drain()
print("Close the client socket")
writer.close()
self.dataReady.emit(reply)
class Widget(QtWidgets.QWidget):
dataChanged = QtCore.pyqtSignal(object)
def __init__(self, parent=None):
super().__init__(parent)
self.m_lineedit = QtWidgets.QLineEdit()
button = QtWidgets.QPushButton("Send", clicked=self.onClicked)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.m_lineedit)
lay.addWidget(button)
self.m_web = WebServer()
self.m_web.startServer()
self.dataChanged.connect(self.m_web.setData)
@QtCore.pyqtSlot()
def onClicked(self):
text = self.m_lineedit.text()
self.dataChanged.emit(text)
@QtCore.pyqtSlot(object)
def onDataReady(self, data):
print(data)
def closeEvent(self, event):
self.m_web.stop()
super().closeEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Related Topics
How to Break a Long Line to Multiple Lines in Python
How to Get a Complete List of Object's Methods and Attributes
Group by & Count Function in SQLalchemy
Efficient Numpy 2D Array Construction from 1D Array
How to Read File N Lines at a Time
Word Count from a Txt File Program
Python 3.5 - "Geckodriver Executable Needs to Be in Path"
How to Add a New Column to a Spark Dataframe (Using Pyspark)
Schedule Python Script - Windows 7
A Tool to Convert Matlab Code to Python
How to Catch a Numpy Warning Like It's an Exception (Not Just for Testing)
Which Is More Preferable to Use: Lambda Functions or Nested Functions ('Def')
How to Get the Nth Element of a Python List or a Default If Not Available
How to Display a Pandas Data Frame with Pyqt5/Pyside2
Force Numpy Ndarray to Take Ownership of Its Memory in Cython
How to Get the Correct Dimensions for a Pygame Rectangle Created from an Image