Pyqt4 Wait in Thread for User Input from Gui

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 this QThread to a slot in the QMainWindow that will display the popup (using QDialog.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:

  1. 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.
  2. 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.
  3. 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



Leave a reply



Submit