Use QThread to periodically update a QTableWidget pyqt - python

In my application, I'm fetching records using an API call and then I add the data to a QTableWidget dynamically. Here is a snippet of my code so far:
class TriageUI(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = Ui_TriageWindow()
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
self.move(QtGui.QApplication.desktop().screen().rect().center()- self.rect().center())
self.ui.setupUi(self)
self.update_records()
def update_records(self):
#items are the results from the API fetch
items = json.loads(get_triage_queue(COOKIES, SERVER, PORT))
rows = len(items['objects'])
self.ui.tableWidget.setColumnCount(5)
self.ui.tableWidget.setRowCount(rows)
index = 0
column = 0
for j in items['objects']:
for key, value in j.iteritems():
f = QtGui.QTableWidgetItem(str(value))
self.ui.tableWidget.setItem(index, column, QtGui.QTableWidgetItem(f))
column = column + 1
However, I want to be able to make the API call for data periodically(e.g after 15 seconds) and then add any new data items in the results to the table. How can I achieve this.
Thank you in advance.

Here you have an example of doing repetitive calls to a class member function (that could be your update_records function) using a PyQt4.QtCore.QTimer. Some times the solution to a problem is more easy than we think.
Note the functions start and stop. This functions makes you able to start and stop the timer at your will.
from PyQt4 import QtGui as gui
from PyQt4 import QtCore as core
class Blinker(gui.QWidget):
def __init__(self, parent=None):
super(Blinker, self).__init__(parent)
self.label = gui.QLabel(self)
self.label.setFixedSize(200, 200)
self.layout = gui.QHBoxLayout(self)
self.layout.addWidget(self.label)
self.timer = core.QTimer(self)
self.timer.setInterval(1000) # Throw event timeout with an interval of 1000 milliseconds
self.timer.timeout.connect(self.blink) # each time timer counts a second, call self.blink
self.color_flag = True
def start(self):
self.timer.start()
def stop(self):
self.timer.stop()
#core.pyqtSlot()
def blink(self):
if self.color_flag:
self.label.setStyleSheet("background-color: blue;")
else:
self.label.setStyleSheet("background-color: yellow;")
self.color_flag = not self.color_flag
if __name__ == '__main__':
import sys
app = gui.QApplication(sys.argv)
w = Blinker()
w.show()
w.start()
sys.exit(app.exec_())

Related

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

Is there a way to make qtimer wait until a function is done?

I have an application in which I would like to do the following thing:
optimize a problem
wait for a certain amount of time, e.g. one minute
measure a certain property
repeat steps two and three several times
start again at 1.)
I want to start the entire process when clicking on a QPushButton. It is necessary that the step 2.) only starts when step 1.) is completely terminated. I dont know how long the optimzation process takes, therefre I cant just use QTimer.sleep().
I have solved this problem the following way:
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QDialog
from PyQt5 import QtWidgets
import sys
class MyForm():
def __init__(self):
self.ui = QDialog()
self.button = QtWidgets.QPushButton(self.ui)
self.button.clicked.connect(self.start_timer)
self.waiting_interval = 10000
self.ui.show()
def start_timer(self):
self.optimize()
self.counter = 0
self.timer = QTimer()
self.timer.timeout.connect(self.tick)
self.timer.setSingleShot(True)
self.timer.start(self.waiting_interval)
def tick(self):
self.timer = QTimer()
if self.counter == 9:
self.timer.timeout.connect(self.start_timer)
else:
self.measure_property()
self.timer.timeout.connect(self.tick)
self.timer.setSingleShot(True)
self.timer.start(self.waiting_interval)
self.counter += 1
def optimize(self):
pass
def measure_property(self):
pass
if __name__ == '__main__':
app = QApplication(sys.argv)
w=MyForm()
app.exec_()
It produces the results that I want but I am looking for a smarter way to do this, maybe using signals and slots. Any help would be appreciated!
The tasks that take a long time are heavy and tend to freeze the GUI giving a bad experience to the user, in these cases those tasks must be executed in another thread:
import sys
from PyQt5 import QtCore, QtWidgets
class ProcessThread(QtCore.QThread):
def run(self):
while True:
self.optimize()
for _ in range(3):
QtCore.QThread.sleep(60)
self.measure_property()
def optimize(self):
print("optimize")
def measure_property(self):
print("measure_property")
class MyForm():
def __init__(self):
self.ui = QtWidgets.QDialog()
self.thread = ProcessThread(self.ui)
self.button = QtWidgets.QPushButton("Press me")
self.button.clicked.connect(self.thread.start)
self.waiting_interval = 10000
lay = QtWidgets.QVBoxLayout(self.ui)
lay.addWidget(self.button)
self.ui.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w=MyForm()
sys.exit(app.exec_())

Pyqt5 GUI Still Hangs When Using Thread

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.

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 show PySide/PyQt UI and auto-run one of its methods?

I wish to open a PySide/PyQt window and automatically start executing a method, which will show a progress in the UI while it is executing.
With the code below, the ui is not shown until the process has completed, thus you cannot see the progress. How can I change the code to see the progress before the process has completed?
from PySide import QtGui
import time
class MyApp(QtGui.QMainWindow)
def __init__(self, parent=None):
super(MyApp, self).__init__(parent)
# Setup
self.centralWidget = QtGui.QWidget(self)
self.setCentralWidget(self.centralWidget)
self.setup_UI()
# Execute process!
self.process()
def setup_UI(self):
''' Attach widgets to window '''
self.mainLayout=QtGui.QVBoxLayout(self.centralWidget)
self.list_widget = QtGui.QListWidget()
self.progress_bar = QtGui.QProgressBar()
self.mainLayout.addWidget(self.list_widget)
self.mainLayout.addWidget(self.progress_bar)
def process(self):
''' Manipulate the ui '''
self.progress_bar.setMaximum(0)
self.progress_bar.setMaximum(10)
for x in range(0, 10):
time.sleep(1)
self.list_widget.addItem('Item ' + str(x))
self.progress_bar.setValue(x)
my_app = MyApp()
my_app.show()
Your main problem that you are blocking qt main thread by calling time.sleep. To solve this issue you have two options. One of them is using threading. Another option is change your code to asynchronous like this:
from PySide import QtGui, QtCore
import time
import sys
class MyApp(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MyApp, self).__init__(parent)
# Setup
self.centralWidget = QtGui.QWidget(self)
self.setCentralWidget(self.centralWidget)
self.setup_UI()
# Execute process!
self.set_process()
self.timer = QtCore.QTimer()
self.i = 0
self.timer.timeout.connect(self.update)
self.timer.start(1000)
def setup_UI(self):
''' Attach widgets to window '''
self.mainLayout=QtGui.QVBoxLayout(self.centralWidget)
self.list_widget = QtGui.QListWidget()
self.progress_bar = QtGui.QProgressBar()
self.mainLayout.addWidget(self.list_widget)
self.mainLayout.addWidget(self.progress_bar)
def set_process(self):
''' Manipulate the ui '''
self.progress_bar.setMaximum(0)
self.progress_bar.setMaximum(10)
def update(self):
if self.i > 9:
self.timer.stop()
self.list_widget.addItem('Item ' + str(self.i))
self.progress_bar.setValue(self.i)
self.i += 1
def main():
app = QtGui.QApplication(sys.argv)
my_win = MyApp()
my_win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
This example are using Qtimer object for updating progress bar with delay.
The example can be made to work quite easily by starting the processing with a single-shot timer and then calling processEvents within the loop to update the GUI:
# Execute process!
QtCore.QTimer.singleShot(100, self.process)
...
for x in range(0, 10):
time.sleep(1)
...
QtGui.qApp.processEvents(QtCore.QEventLoop.AllEvents, 50)
However, there is no guarantee that this type of approach will work with a more realistic example. You may end up needing to use threads or multiprocessing - it all depends on the specific kind of processing you are going to do.

Categories

Resources