Pyqt5 GUI Still Hangs When Using Thread - python

I'm new to python and pyqt.
I'm learning how to use threading with GUI.
I followed this tutorial
http://www.xyzlang.com/python/PyQT5/pyqt_multithreading.html
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import threading
from _ast import While
class Communicate(QObject):
signal = pyqtSignal(int, str)
class My_Gui(QWidget):
def __init__(self):
super().__init__()
self.comm = Communicate()
self.comm.signal.connect(self.append_data)
self.initUI()
def initUI(self):
btn_count = QPushButton('Count')
btn_count.clicked.connect(self.start_counting)
self.te = QTextEdit()
vbox = QVBoxLayout()
vbox.addWidget(btn_count)
vbox.addWidget(self.te)
self.setLayout(vbox)
self.setWindowTitle('MultiThreading in PyQT5')
self.setGeometry(400, 400, 400, 400)
self.show()
def count(self, comm):
'''
for i in range(10):
data = "Data "+str(i)
comm.signal.emit(i, data)
'''
i = 0
while True:
data = "Data "+str(i)
comm.signal.emit(i, data)
i+=1
def start_counting(self):
my_Thread = threading.Thread(target=self.count, args=(self.comm,))
my_Thread.start()
def append_data(self, num, data):
self.te.append(str(num) + " " + data)
if __name__ == '__main__':
app = QApplication(sys.argv)
my_gui = My_Gui()
sys.exit(app.exec_())
I changed the for loop to infinite while loop(incrementing the 'i').
If I execute the program, the GUI still hangs but if I remove the emit signal inside the loop, it no longer hangs.
Are there some tricks to make it not hangs?

while True makes an endless loop in the background
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import threading
from _ast import While
class Communicate(QObject):
signal = pyqtSignal(int, str)
class My_Gui(QWidget):
def __init__(self):
super().__init__()
self.comm = Communicate()
self.comm.signal.connect(self.append_data)
self.initUI()
def initUI(self):
btn_count = QPushButton('Count')
btn_count.clicked.connect(self.start_counting)
self.te = QTextEdit()
vbox = QVBoxLayout()
vbox.addWidget(btn_count)
vbox.addWidget(self.te)
self.setLayout(vbox)
self.setWindowTitle('MultiThreading in PyQT5')
self.setGeometry(400, 400, 400, 400)
self.show()
def count(self, comm):
for i in range(10):
data = "Data "+str(i)
comm.signal.emit(i, data)
# While True below will never stop and cause your program to stuck
'''
i = 0
while True:
data = "Data "+str(i)
comm.signal.emit(i, data)
i+=1
'''
def start_counting(self):
my_Thread = threading.Thread(target=self.count, args=(self.comm,))
my_Thread.start()
def append_data(self, num, data):
self.te.append(str(num) + " " + data)
if __name__ == '__main__':
app = QApplication(sys.argv)
my_gui = My_Gui()
sys.exit(app.exec_())

I think you are getting downvoted for two things:
you did only copy and paste the code from the tutorial
you didn't read the tutorial
In the tutorial, the author states:
In the above example, we have created a QPushbutton and QTextEdit. When the button is clicked it creates a new Thread that counts from 0 to 9 and emits the signal in order to append the number and data in the QTextEdit. In class Communicate signal is initialized as pyqtSignal(int, str). This int and str means when a signal will be emitted it will also pass two arguments the first one will be of Integer type and second one will be of String type.
By changing the loop to while true you continuosly emit signals and append the text in the QTextEdit. Probably not what you want.
Also commenting the emit statement internally continues to run the while loop.

Related

How can I return my session variable to my main code with PyQt5?

I don't know how to express my problem in a question because I am a very PyQt5 learner and I don't know the vocabularies, but come on.
I need to return the session_user variable (in the login () function) to my main code. This variable needs to be passed to the main screen after the login is completed, but I don't know of a function in PyQt5 that returns a variable that is not of type int like: accept (), reject (), done ().
Is it possible to return my session_user variable to my main code with some PyQt5 function like the ones I mentioned?
my code:
from PyQt5 import uic, QtWidgets
from PyQt5.QtWidgets import QDialog
from views.ui.login.telaLogin import Ui_Dialog
import sys
from control.exception_login import verification_login_user
class ViewLogin(QDialog):
def __init__(self):
super().__init__()
self.viewlogin = Ui_Dialog()
self.viewlogin.setupUi(self)
self.viewlogin.button_login.clicked.connect(self.login)
def login(self):
self.login = self.viewlogin.login_login.text()
self.password = self.viewlogin.login_password.text()
erro = verification_login_user(self.login, self.password)
if (erro == False):
self.close()
session_user = (self.login, self.senha)
#self.done(r) # I understand its functionality # PROBLEM
#return (session_user) # PROBLEM
self.accept() # The problem would end here if I didn't need the variable sessao_user
elif(erro == True):
self.viewlogin.login_login.setText('')
self.viewlogin.login_password.setText('')
app = QtWidgets.QApplication([])
w = ViewLogin()
result = w.exec_()
print(result) # desired result = (' login', 'password')
# real result = 1
sys.exit(app.exec_())
One of the approaches you could implement is via threading module.
Create a class Controller and Daemon to True (this ensures the thread will be killed after the app closes).
class Controller(threading.Thread):
def __init__(self):
super(Controller, self).__init__(daemon = True)
self.my_variable = 0
self.variables_from_gui = None
Overwrite the run method:
def run(self):
print("My main loop is here!")
while True:
print("Button presses: {}, Variables from gui: {}".format(self.button_presses, self.variables_from_gui))
time.sleep(1) #This wont freeze the gui
Add a function that will register the button login click:
def login(self, variables_from_gui = None):
#Do you login stuff
self.variables_from_gui = variables_from_gui
self.button_presses += 1
In the App class (in your case that would be the main GUI class) from the login function start a new thread and pass variables to it.
def login(self):
#Stuff to do in your GUI
variables_to_send = 0, "Text", {"key" : 10}
threading.Thread(target = lambda: self.controller.login(variables_from_gui = variables_to_send)).start()
Also in your GUI class create a Controller object and start it:
self.controller = Controller()
self.controller.start() #Start the run method in the Controller()
There are plenty of things that can be done better and make it more Threading safe. But that is outside of this question topic.
Whole code:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import pyqtSlot
import threading
import time
class App(QWidget):
def __init__(self):
super().__init__()
self.left = 10
self.top = 10
self.width = 320
self.height = 200
self.initUI()
def initUI(self):
self.controller = Controller()
self.controller.start() #Start the run method in the Controller()
self.setGeometry(self.left, self.top, self.width, self.height)
button = QPushButton("My login", self)
button.move(100,70)
button.clicked.connect(self.login)
self.show()
def login(self):
#Stuff to do in your GUI
variables_to_send = 0, "Text", {"key" : 10}
threading.Thread(target = lambda: self.controller.login(variables_from_gui = variables_to_send)).start()
class Controller(threading.Thread):
def __init__(self):
super(Controller, self).__init__(daemon = True)
self.button_presses = 0
self.variables_from_gui = None
def run(self):
print("My main loop is here!")
while True:
print("Button presses: {}, Variables from gui: {}".format(self.button_presses, self.variables_from_gui))
time.sleep(1) #This wont freeze the gui
def login(self, variables_from_gui = None):
#Do you login stuff
self.variables_from_gui = variables_from_gui
self.button_presses += 1
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())

Passing multiple parameters back from PyQt thread

Is there a way to pass multiple parameters from a thread back to the main thread at the same time?
I have started a thread using PyQt5 in which two real time variables are calculated. I want to pass both parameters back to the main thread to be combined and calculated with parameters from another thread.
As attached in the code below, I am able to return each parameter individually and print to the screen. How do I return all parameters into one function so I can proceed with calculations from another thread?
Thank you!
import sys
import time
from PyQt5.QtWidgets import QMainWindow, QPushButton, QVBoxLayout, QFrame, QApplication
from PyQt5.QtCore import pyqtSignal, QObject, QThread
class Counter(QObject):
'''
Class intended to be used in a separate thread to generate numbers and send
them to another thread.
'''
param1 = pyqtSignal(str)
param2 = pyqtSignal(str)
stopped = pyqtSignal()
def __init__(self):
QObject.__init__(self)
def start(self):
'''
Count from 0 to 99 and emit each value to the GUI thread to display.
'''
for x in range(4):
self.param1.emit(str(x))
self.param2.emit(str(x)+'2')
time.sleep(0.7)
self.stopped.emit()
class Application(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
# Configuring widgets
self.button = QPushButton()
self.button.setText('99')
self.layout = QVBoxLayout()
self.layout.addWidget(self.button)
self.frame = QFrame()
self.frame.setLayout(self.layout)
self.setCentralWidget(self.frame)
# Configuring separate thread
self.counterThread = QThread()
self.counter = Counter()
self.counter.moveToThread(self.counterThread)
# Connecting signals
self.button.clicked.connect(self.startCounting)
self.counter.param1.connect(self.button.setText)
self.counter.param1.connect(self.someFunction1)
self.counter.param2.connect(self.someFunction2)
self.counter.stopped.connect(self.counterThread.quit)
self.counterThread.started.connect(self.counter.start)
# print data from parameter 1
def someFunction1(self, data):
print(data + ' in main')
# print data from parameter 2
def someFunction2(self, data):
print(data + ' in main')
def startCounting(self):
if not self.counterThread.isRunning():
self.counterThread.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Application()
window.show()
sys.exit(app.exec_())
The signals also support the transmission of lists so you can use it to transport several variables:
class Counter(QObject):
"""
Class intended to be used in a separate thread to generate numbers and send
them to another thread.
"""
params = pyqtSignal(list)
stopped = pyqtSignal()
def start(self):
"""
Count from 0 to 99 and emit each value to the GUI thread to display.
"""
for x in range(4):
values = [str(x), str(x) + "2"]
self.params.emit(values)
time.sleep(0.7)
self.stopped.emit()
class Application(QMainWindow):
def __init__(self):
super(Application, self).__init__()
# Configuring widgets
self.frame = QFrame()
self.button = QPushButton("99")
lay = QVBoxLayout(self.frame)
lay.addWidget(self.button)
self.setCentralWidget(self.frame)
# Configuring separate thread
self.counterThread = QThread()
self.counter = Counter()
self.counter.moveToThread(self.counterThread)
# Connecting signals
self.button.clicked.connect(self.startCounting)
self.counter.params.connect(self.someFunction)
self.counter.stopped.connect(self.counterThread.quit)
self.counterThread.started.connect(self.counter.start)
#pyqtSlot(list)
def someFunction(self, params):
print(params)
if params:
self.button.setText(params[0])
def startCounting(self):
if not self.counterThread.isRunning():
self.counterThread.start()

PyQT GUI Freeze with Infinite Loop in QThread

I'm new to PyQT and QThread. My PyQT program has a button which trigger a continuous writing/reading of the serial port every second.
Problem: When the button is clicked and the looping starts, the GUI freezes up. Am I using QThread wrongly?
import sys
from PyQt4.QtGui import *
from PyQt4 import QtCore
import serial
import time
from TemperatureReader import TemperatureReader # my module to talk to serial hardware
class Screen(QMainWindow):
def __init__(self):
super(Screen, self).__init__()
self.initTemperatureReader()
self.initUI()
def initTemperatureReader(self):
ser = serial.Serial(port='COM9', baudrate=115200, timeout=5)
self.temperatureReader = TemperatureReader(ser)
def initUI(self):
startReadingTCsBtn = QPushButton('Start Reading')
startReadingTCsBtn.clicked.connect(self.startReadingTCsThread)
startReadingTCsBtn.show()
directControlBoxLayout = QVBoxLayout()
directControlBoxLayout.addWidget(startReadingTCsBtn)
self.mainFrame = QWidget()
mainLayout = QVBoxLayout()
mainLayout.addWidget(directControlGroupBox)
self.mainFrame.setLayout(mainLayout)
self.setCentralWidget(self.mainFrame)
self.setGeometry(300,300,400,150)
self.show()
def startReadingTCsThread(self):
self.tcReaderThread = TCReaderThread(self.temperatureReader)
self.tcReaderThread.temperatures.connect(self.onTemperatureDataReady)
self.tcReaderThread.start()
def onTemperatureDataReady(self, data):
print data
class TCReaderThread(QtCore.QThread):
temperatures = QtCore.pyqtSignal(object)
def __init__(self, temperatureReader):
QtCore.QThread.__init__(self)
self.temperatureReader = temperatureReader
def run(self):
while True:
resultString = self.temperatureReader.getTemperature() # returns a strng
self.temperatures.emit(resultString)
time.sleep(1)
def main():
app = QApplication(sys.argv)
screen = Screen()
app.exec_()
if __name__ == '__main__':
main()

Filling QListWidget object using Multi Threading

I have 2 QListWidget list objects, first one contain some data before showing off main GUI, second one is filling with another data when something has been selected from the first list... I'm trying to fill the second list with 1 million items using multi-threading to not freeze the main GUI windows while that task is in process.
self.lst1= QtGui.QListWidget(self.groupBox)
self.lst2= QtGui.QListWidget(self.groupBox)
self.lst1.itemSelectionChanged.connect(lambda: self.thread_list_filler(idx = 0))
def thread_list_filler(self, idx):
if idx == 0:
th = Thread(target = self.fill_List2)
th.start()
def fill_List2(self):
self.lst2.clear()
for i in range(1,1000000+1):
self.lst2.addItem(str(i))
The GUI is crashing every time when i press some item from lst1, whats the problem and how to avoid this?
You're not supposed to interact with gui elements outside the main thread. I.e. you should emit a signal in the thread, and connect this signal to a slot which will do the actual adding-to-list business.
Note however that 1 million items is a HUGE amout of data to put in a QListWidget.
Anyway, something like that may work:
class MyWidget(QtGui.QWidget):
addRequested = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
layout = QtGui.QVBoxLayout(self)
self.groupBox = QtGui.QGroupBox('Test', self)
layout.addWidget(self.groupBox)
vlayout = QtGui.QVBoxLayout(self.groupBox)
self.button = QtGui.QPushButton("Fill it", self.groupBox)
self.lst2 = QtGui.QListWidget(self.groupBox)
vlayout.addWidget(self.button)
vlayout.addWidget(self.lst2)
self.button.clicked.connect(self.thread_list_filler)
self.addRequested.connect(self.lst2.addItem)
def thread_list_filler(self):
self.lst2.clear()
th = threading.Thread(target = self.fill_List2)
th.start()
def fill_List2(self):
for i in range(1,1000000+1):
self.addRequested.emit(str(i))
Even though it's been awhile since i asked this question, here is a solution for it which suits my problem very well.
from PyQt4 import QtGui, QtCore
from qTest import Ui_Form
import sys
from time import sleep
class WorkerThread(QtCore.QThread):
def __init__(self, parent):
super(WorkerThread, self).__init__(parent)
self.stopFlag = False
def run(self):
for i in xrange(0, 1000000):
if self.stopFlag:
break
self.emit(QtCore.SIGNAL('addIntoList(int)'), i)
sleep(0.001)
self.stopFlag = False
def stop(self):
self.stopFlag = True
class TEST(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton.clicked.connect(self.stopThread)
self.ui.pushButton_2.clicked.connect(self.close)
self.lst1 = self.ui.listWidget_1
self.lst2 = self.ui.listWidget_2
self.qThread = WorkerThread(self)
self.connect(self.qThread, QtCore.SIGNAL("addIntoList(int)"), self.addIntoList)
for i in range(10):
self.lst1.addItem("%d" % i)
self.lst1.currentRowChanged.connect(self.thread_list_filler)
#QtCore.pyqtSlot(int)
def addIntoList(self, item):
self.lst2.addItem(str(item))
def stopThread(self):
self.qThread.stop()
def thread_list_filler(self, row):
if self.qThread.isRunning():
self.qThread.stop()
self.qThread.wait()
self.lst2.clear()
if row == 0:
self.qThread.start()
QtGui.QApplication.setStyle('cleanlooks')
font = QtGui.QFont()
font.setPointSize(10)
font.setFamily('Arial')
app = QtGui.QApplication(sys.argv)
app.setAttribute(QtCore.Qt.AA_DontShowIconsInMenus,False)
app.setFont(font)
window = TEST()
window.show()
sys.exit(app.exec_())

How to use multiprocessing.Pool correctly with PySide to create a non-blocking GUI

I am try to use multiprocessing to create a non-blocking GUI. The function Multiprocessing.Pool.appy_async() allows a callback function to be added, making it easy to update the main GUI after a time-intensive operation has been completed. However, the following code still blocks when clicking on button1. How can I modify this so that while the button1 callback is executing, button2 still responds. I am running python 2.7 and multiprocessing 0.70a1.
from PySide.QtCore import *
from PySide.QtGui import *
import multiprocessing
import time
import sys
def f(x):
'''This is a time-intensive function
'''
y = x*x
time.sleep(2)
return y
class MainWindow(QMainWindow): #You can only add menus to QMainWindows
def __init__(self):
super(MainWindow, self).__init__()
self.pool = multiprocessing.Pool(processes=4)
button1 = QPushButton('Connect', self)
button1.clicked.connect(self.apply_connection)
button2 = QPushButton('Test', self)
button2.clicked.connect(self.apply_test)
self.text = QTextEdit()
vbox1 = QVBoxLayout()
vbox1.addWidget(button1)
vbox1.addWidget(button2)
vbox1.addWidget(self.text)
myframe = QFrame()
myframe.setLayout(vbox1)
self.setCentralWidget(myframe)
self.show() #display and activate focus
self.raise_()
def apply_connection(self):
result = self.pool.apply_async(f, [10], callback=self.update_gui)
result.get(3)
def update_gui(self, result):
self.text.append('Applied connection. Result = %d\n' % result)
def apply_test(self):
self.text.append('Testing\n')
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = MainWindow()
app.exec_()
result.get(3) blocks for 3 seconds to wait for the result. Don't call get, the callback will handle the result.

Categories

Resources