data transfer problem between qthread and ui? - python

I succeeded in creating a gui crawling program using Beautiful Soup and PyQt5.
By the way, I had a problem with gui freeze while the program executed the repeating statement.
So I'm going to use QThread.
But when I bring the elements related to gui on Thread, there is a problem.
(There's no problem with operating code that has nothing to do with gui, so I don't think there's any data transmission between classes.) (Is this right?)
I've created a simple problem. ↓
import sys
import time
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
form_class = uic.loadUiType('aaaa.ui')[0]
class Thread1(QThread):
def __init__(self, Main):
super().__init__(Main)
def run(self):
i = 1
while i <= 10:
print(self.lineEdit.text().strip()) #No data transmission between Main and Thread1??
time.sleep(1)
i += 1
class Main(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)
self.initSetting()
self.initSignal()
def initSetting(self):
self.statusBar().showMessage('Wait')
self.setWindowTitle('aaaa')
def initSignal(self):
self.pushButton.clicked.connect(self.printWord)
def printWord(self):
self.statusBar().showMessage('Threading')
x = Thread1(self)
x.start()
if __name__ == "__main__" :
app = QApplication(sys.argv)
aaaa = Main()
aaaa.show()
app.exec_()

Using QThread, from what I understand, transmission of information between classes is doable, but to transmit information from your Main class to your Thread1 class, you have to pass arguments upon instantiation of your Thread1 class from your Main class.
In other words, your Main class and your Thread1 class do not share any variables or functions. They are separate classes.
Here's how I might do this:
class Main(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)
self.initSetting()
self.initSignal()
def initSetting(self):
self.statusBar().showMessage("Wait")
self.setWindowTitle("aaaa")
def initSignal(self):
self.pushButton.clicked.connect(self.printWord)
def printWord(self):
self.statusBar().showMessage("Threading")
some_message = self.lineEdit.text().strip()
self.thread_1 = Thread1(some_message)
self.thread_1.start()
class Thread1(QThread):
def __init__(self, input_message, parent=None):
QThread.__init__(self, parent)
self.input_message = input_message
def run(self):
i = 1
while i <= 10:
print(self.input_message)
time.sleep(1)
i += 1
Let me know if this is helpful.

Related

Python program subclasses a PyQt5 Window cant setwindow.title in Function

I have a sample Python program that sub classes a PyQt5 window. I am teaching myself and still a little new PyQt5 and python Classes.
The program does what I want to do I ran into an error that I don't understand how to fix. To start this program functions as is, I am currently learning how to run Threads. I imported and sub classed the PyQt5 window. In the __init__ section of the subclass I can set the window title and it works fine.
If I move the statement to a function "def initUI(self):" I am unable to set the window title, mind you I have tried various versions of the statement and nothing works. It's the first line of the def and I have it commented out.
My questions are:
Is this property setable in a def.
If it io what is the proper format of the statement.
from PyQt5.QtCore import pyqtSlot, pyqtSignal
from PyQt5 import QtCore, QtGui, QtWidgets
from Threads import Ui_MainWindow
import sys, time
from time import sleep
class MainWindow_EXEC():
def __init__(self): # This section has to be laid out this way
app = QtWidgets.QApplication(sys.argv)
win = QtWidgets.QMainWindow()
self.ui = Ui_MainWindow()
self.ui.setupUi(win)
self.initUI() # Inits that need to happen before start up
win.setWindowTitle("This is the Title!") # Works fine
win.resize(800,600)
win.show()
sys.exit(app.exec_())
def initUI(self): # Button assignments
#self.ui.setWindowTitle("This is the Title!") # AttributeError: 'Ui_MainWindow' object has no attribute 'setWindowTitle'
self.ui.btn_Start.clicked.connect(self.start_progressbar)
self.ui.btn_Stop.clicked.connect(self.stop_progressbar)
self.ui.btn_Reset.clicked.connect(self.reset_progressbar)
self.progress_value = 0
self.stop_progress = False
def progressbar_counter(self, start_value=0):
# have to use member: self.run_thread NOT local var: run_thread
self.run_thread = RunThread(parent=None, counter_start=start_value)
self.run_thread.start()
self.run_thread.counter_value.connect(self.get_thread_value)
def get_thread_value(self, counter): #This updates the progress bar
print(counter)
if not self.stop_progress:
self.ui.progressBar.setValue(counter)
def start_progressbar(self): # This is the button that starts the progress bar
self.stop_progress = False # This is a switch
self.progress_value = self.ui.progressBar.value() # Updating the progress bar
self.progressbar_counter(self.progress_value)
def stop_progressbar(self): # This is a button to stop the progress bar
self.stop_progress = True
self.run_thread.stop()
def reset_progressbar(self): # This is a button to reset the progress bar
self.stop_progressbar()
self.progress_value = 0
self.stop_progress = False
self.ui.progressBar.reset()
class RunThread(QtCore.QThread):
counter_value = QtCore.pyqtSignal(int) # define new Signal
def __init__(self, parent=None, counter_start=0):
super(RunThread, self).__init__(parent)
self.counter = counter_start
self.isRunning = True
def run(self):
while self.counter < 100 and self.isRunning == True:
sleep(0.1)
self.counter += 1
print(self.counter)
self.counter_value.emit(self.counter) # emit new Signal with value
def stop(self):
self.isRunning = False
print('stopping thread...')
self.terminate()
if __name__ == "__main__":
MainWindow_EXEC()
The objective of the following classes must be distinguished:
QMainWindow is a widget that has the setWindowTitle method.
Ui_MainWindow is not a widget but a class that is used to fill a widget so it does not have the setWindowTitle method.
The solution is to make win a class member and then use that object to modify the title:
class MainWindow_EXEC():
def __init__(self): # This section has to be laid out this way
app = QtWidgets.QApplication(sys.argv)
self.win = QtWidgets.QMainWindow()
self.ui = Ui_MainWindow()
self.ui.setupUi(self.win)
self.initUI()
self.win.setWindowTitle("This is the Title!")
self.win.resize(800,600)
self.win.show()
sys.exit(app.exec_())
def initUI(self): # Button assignments
self.win.setWindowTitle("This is the Title!")
self.ui.btn_Start.clicked.connect(self.start_progressbar)
# ...

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

PyQt: nested inheritance setupUi

I'm trying to make a custom widget getting more and more descriptive with each sub class. For instance below, I'm adding to a list of boxes as I make them. I want do_stuff() to only run once but I want it to run on initialization. My problem is that I need all of the __setupUi() functions to run before I finish it. Any help is appreciated.
from PyQt5 import QtCore, QtWidgets
class layer4:
def __init__(self):
self.boxes = []
self.__setupUi()
# I want to run this once AFTER all of the children (regardless of how deep it goes) have run
self.do_stuff()
def __setupUi(self):
self.qsb = QtWidgets.QSpinBox()
self.boxes.append(self.qsb)
def do_stuff(self):
print(self.boxes)
class layer3(layer4):
def __init__(self):
super().__init__()
self.__setupUi()
def __setupUi(self):
self.qsb2 = QtWidgets.QSpinBox()
self.boxes.append(self.qsb2)
class layer2(layer3):
def __init__(self):
super().__init__()
self.__setupUi()
def __setupUi(self):
self.datetimebox = QtWidgets.QDateTimeEdit()
self.boxes.append(self.datetimebox)
class layer1(layer2):
def __init__(self):
super().__init__()
self.__setupUi()
def __setupUi(self):
self.other = QtWidgets.QDateTimeEdit()
self.boxes.append(self.other)
So if I run
t = layer2()
It should return 2 QSpinboxes and 1 datetimeedit

PyQt update gui

I'm trying to update the text in a Qt GUI object via a QThread in PyQt but I just get the error QPixmap: It is not safe to use pixmaps outside the GUI thread, then it crashes. I would really appreciate any help, thanks.
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent = None):
QMainWindow.__init__(self, parent)
self.setupUi(self)
self.output = Output()
def __del__ (self):
self.ui = None
#pyqtSignature("")
def on_goBtn_released(self):
threadnum = 1
#start threads
for x in xrange(threadnum):
thread = TheThread()
thread.start()
class Output(QWidget, Ui_Output):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.setupUi(self)
self.ui = Ui_Output
self.show()
def main(self):
self.textBrowser.append("sdgsdgsgsg dsgdsg dsgds gsdf")
class TheThread(QtCore.QThread):
trigger = pyqtSignal()
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
self.trigger.connect(Output().main())
self.trigger.emit()
self.trigger.connect(Output().main())
This line is problematic. You are instantiating a class in the thread which looks like a widget. This is wrong. You shouldn't use GUI elements in a different thread. All GUI related code should run in the same thread with the event loop.
The above line is also wrong in terms of design. You emit a custom signal from your thread and this is a good way. But the object to process this signal should be the one that owns/creates the thread, namely your MainWindow
You also don't keep a reference to your thread instance. You create it in a method, but it is local. So it'll be garbage collected, you probably would see a warning that it is deleted before it is finished.
Here is a minimal working example:
import sys
from PyQt4 import QtGui, QtCore
import time
import random
class MyThread(QtCore.QThread):
trigger = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(MyThread, self).__init__(parent)
def setup(self, thread_no):
self.thread_no = thread_no
def run(self):
time.sleep(random.random()*5) # random sleep to imitate working
self.trigger.emit(self.thread_no)
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.text_area = QtGui.QTextBrowser()
self.thread_button = QtGui.QPushButton('Start threads')
self.thread_button.clicked.connect(self.start_threads)
central_widget = QtGui.QWidget()
central_layout = QtGui.QHBoxLayout()
central_layout.addWidget(self.text_area)
central_layout.addWidget(self.thread_button)
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)
def start_threads(self):
self.threads = [] # this will keep a reference to threads
for i in range(10):
thread = MyThread(self) # create a thread
thread.trigger.connect(self.update_text) # connect to it's signal
thread.setup(i) # just setting up a parameter
thread.start() # start the thread
self.threads.append(thread) # keep a reference
def update_text(self, thread_no):
self.text_area.append('thread # %d finished' % thread_no)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mainwindow = Main()
mainwindow.show()
sys.exit(app.exec_())

Categories

Resources