Here is some sample code that breaks:
import sys
import time
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar)
class Actions(QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.show()
self.count = 0
while self.count < 100:
self.count += 1
time.sleep(1) # Example external function
self.progress.setValue(self.count)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
Running this will cause it to freeze and become unresponsive particularly in windows environments. Replacing the time.sleep function with any non-PyQt5 function will yield the same results.
From what I understand this has to do with the function not being called in a separate thread using QThread. I used this answer as a reference and came up with a partial solution.
import sys
import time
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar)
class External(QThread):
def run(self):
count = 0
while count < 100:
count += 1
print(count)
time.sleep(1)
class Actions(QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
calc = External()
calc.finished.connect(app.exit)
calc.start()
sys.exit(app.exec_())
This will run time.sleep in the background and keep the main window responsive. But, I don't know how to update the values using self.progress.setValue since it's not accessible in class External.
So far from what I know, I have to use signals to accomplish this. Most of the documentation out there is for PyQt4 making it harder to find a solution.
Another problem I am faced with is being able to start the External thread from within class Actions.
Answers to this problem will also serve as valuable documentation for PyQt5.
Thanks in advance.
You must use the signals to update the values.
import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar)
class External(QThread):
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < 100:
count += 1
self.countChanged.emit(count)
print(count)
time.sleep(1)
class Actions(QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.show()
def onCountChanged(self, value):
self.progress.setValue(value)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
calc = External()
calc.countChanged.connect(window.onCountChanged)
calc.start()
sys.exit(app.exec_())
Here is a version that starts the thread from inside class Actions and uses a button:
import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
class External(QThread):
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < 100:
count += 1
time.sleep(1)
print(count)
self.countChanged.emit(count)
class Actions(QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
self.calc = External()
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
def onCountChanged(self, value):
self.progress.setValue(value)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
Related
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import time
import sys
import numpy as np
class Mainthread(QThread):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.running = None
self.mutex = QMutex()
def run(self):
while self.running:
self.mutex.lock()
print ("test")
time.sleep(1)
self.mutex.unlock()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.mainthread = Mainthread(self)
self.mainthread.running = True
self.mainthread.start()
self.mainthread1 = Mainthread(self)
self.mainthread1.running = True
self.mainthread1.start()
app = QApplication(sys.argv)
mainwindow = MainWindow()
mainwindow.show()
app.exec_()
I have this code where I run two instances of the same MainThread class.
What I was expecting was that mainthread's message (which is "test") would print, then wait for a sec and then mainthread1's would be printed. Instead, it seems like both threads are running at the same time. Is there something I'm missing?
In your code, each thread creates its own separate mutex, so no relation is enforced between the two. Create a single mutex first, and pass it to the theads:
import time
import sys
from PyQt5.QtCore import QThread, QMutex
from PyQt5.QtWidgets import QMainWindow, QApplication
class Mainthread(QThread):
def __init__(self, mutex, parent):
super().__init__(parent)
self.parent = parent
self.running = None
self.mutex = mutex
def run(self):
while self.running:
self.mutex.lock()
print ("test")
time.sleep(1)
self.mutex.unlock()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
mutex = QMutex()
self.mainthread = Mainthread(mutex, self)
self.mainthread.running = True
self.mainthread.start()
self.mainthread1 = Mainthread(mutex, self)
self.mainthread1.running = True
self.mainthread1.start()
app = QApplication(sys.argv)
mainwindow = MainWindow()
mainwindow.show()
app.exec_()
Note: I don't have PyQt5 installed (and doing so on my architecture is tricky), but I tested this in PySide6 and as far as I know the behavior should be consistent.
This question already has answers here:
In PyQt, what is the best way to share data between the main window and a thread
(1 answer)
Background thread with QThread in PyQt
(7 answers)
Example of the right way to use QThread in PyQt?
(3 answers)
Closed 4 months ago.
I'm trying to make Typing speed test app in Pyqt, but it recently started crashing when I was inside QLineEdit. Sometimes it crashed instantly after I tried typing, sometimes only after tens of character were typed.
My code:
from PyQt5.QtWidgets import QWidget, QMainWindow, QApplication, QStackedWidget, QPushButton, QSizePolicy, QLabel, QLineEdit, QHBoxLayout, QVBoxLayout
from PyQt5.QtCore import *
import sys
import time
import random
import threading
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle("Typing Speed Test App")
self.setMinimumSize(1280,720)
global stacked
stacked = QStackedWidget(self)
self.setCentralWidget(stacked)
stacked.addWidget(Menu())
stacked.addWidget(TS_Test())
stacked.addWidget(Statistics())
stacked.setCurrentIndex(1) # test only
class Menu(QWidget):
def __init__(self):
QWidget.__init__(self)
self.frameWidth = self.frameSize().width()
self.frameHeight = self.frameSize().height()
self.initUI()
def initUI(self):
self.button_test = QPushButton(self)
self.button_graph = QPushButton(self)
self.button_test.setFixedWidth(50)
self.button_test.clicked.connect(lambda: stacked.setCurrentIndex(1))
self.button_graph.clicked.connect(lambda: stacked.setCurrentIndex(2))
self.button_graph.clicked.connect(lambda: print(self.frameSize().width()))
self.button_test.move(self.frameSize().width()*50//100-50,200)
self.button_graph.move(200,230)
class TS_Test(QWidget):
def __init__(self):
QWidget.__init__(self)
f = open('paragraphs.txt').read()
self.sentences = f.split('BREAK\n')
self.sentence = random.choice(self.sentences)
self.sentence = self.sentence.strip('\n')
self.word = self.sentence.split()
self.setStyleSheet("QLabel{font-size: 15px;}")
self.initUI()
self.start_thread()
def initUI(self):
self.button_back = QPushButton(self)
self.button_back.clicked.connect(lambda: stacked.setCurrentIndex(0))
self.button_back.move(30,50)
self.lineEdit = QLineEdit()
self.label = QLabel()
self.accuracy_label = QLabel()
self.wpm_label = QLabel()
self.first_letter = self.sentence[0]
self.layout = QVBoxLayout(self)
self.layout.addWidget(self.label)
self.layout.addWidget(self.lineEdit)
self.layout.addWidget(self.accuracy_label)
self.layout.addWidget(self.wpm_label)
self.label.setText(self.sentence)
self.layout.setContentsMargins(250,250,250,300)
self.setLayout(self.layout)
def start_thread(self):
self.t_start=threading.Thread(target=self.start)
self.t_start.start()
def start(self):
while True:
if len(self.lineEdit.text()) > 0:
if self.lineEdit.text()[0] == self.first_letter:
self.time_thread()
break
def time_thread(self):
print('start')
timer_start = time.perf_counter()
self.correct_char = 0
while True:
if (len(self.lineEdit.text()) == len(self.sentence)) and (self.lineEdit.text().split()[-1] == self.word[-1]):
self.written_word = self.lineEdit.text().split(' ')
timer_stop = time.perf_counter()
timer = timer_stop - timer_start
self.wpm = len(self.written_word) / timer * 60
for i in range(len(self.sentence)):
if self.lineEdit.text()[i] == self.sentence[i]:
self.correct_char += 1
self.accuracy = self.correct_char / len(self.sentence) * 100
print(f"Accuracy = {self.correct_char / len(self.sentence) * 100}")
print(f'WPM: {self.wpm:0.3f}')
self.accuracy_label.setText(f'Accuracy = {self.accuracy}%')
self.wpm_label.setText(f'WPM: {self.wpm:0.3f}')
break
class Statistics(QWidget):
def __init__(self):
QWidget.__init__(self)
self.initUI()
def initUI(self):
self.button_back = QPushButton(self)
self.button_back.clicked.connect(lambda: stacked.setCurrentIndex(0))
self.button_back.move(400,300)
if __name__ == "__main__":
app = QApplication(sys.argv)
mainwin = MainWindow()
mainwin.show()
sys.exit(app.exec_())
I've tried doing try and except so that it can print me traceback error, but even that didn't print anything. I read somewhere that it might be because I'm using library threading instead of QThread, but I have no idea if that has something to do with it.
EDIT:
Content in paragraphs.txt:
This line serves as test.
BREAK
Sentence number one.
BREAK
Sentence number two.
BREAK
Line number three.
This is what I get when I click on the OK button.
But I would like to disable the two buttons while the run_action() is still running and finally reset the bar to 0.
This is my current code:
import sys
import time
from PySide6.QtCore import QThread, Signal
from PySide6.QtWidgets import (
QApplication,
QHBoxLayout,
QProgressBar,
QPushButton,
QWidget,
)
class External(QThread):
progressChanged = Signal(int)
def run(self):
progress = 0
while progress < 100:
progress += 10
time.sleep(1)
self.progressChanged.emit(progress)
class Window(QWidget):
"""The main application Window."""
def __init__(self):
super().__init__()
self.setWindowTitle("Example")
self.layout = QHBoxLayout()
self.layout.setContentsMargins(6, 6, 6, 6)
self.bar = QProgressBar()
self.bar.setTextVisible(False)
self.bar.setValue(0)
self.layout.addWidget(self.bar)
self.cancel_btn = QPushButton("Cancel")
self.cancel_btn.clicked.connect(self.close)
self.layout.addWidget(self.cancel_btn)
self.ok_btn = QPushButton("OK")
self.ok_btn.clicked.connect(self.run_action)
self.layout.addWidget(self.ok_btn)
self.setLayout(self.layout)
def run_action(self):
self.ok_btn.setEnabled(False)
self.cancel_btn.setEnabled(False)
self.calc = External()
self.calc.progressChanged.connect(self.onProgressChanged)
self.calc.start()
self.cancel_btn.setEnabled(True)
self.ok_btn.setEnabled(True)
self.bar.setValue(0)
def onProgressChanged(self, value):
self.bar.setValue(value)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
void QThread::finished()
This signal is emitted from the associated thread right before it finishes executing.
When this signal is emitted, the event loop has already stopped running.
No more events will be processed in the thread, except for deferred deletion events.
This signal can be connected to QObject::deleteLater(), to free objects in that thread.
import sys
#import time
#from PySide6.QtCore import QThread, Signal
from PyQt5.QtCore import QThread, pyqtSignal
#from PySide6.QtWidgets import (
from PyQt5.QtWidgets import (
QApplication,
QHBoxLayout,
QProgressBar,
QPushButton,
QWidget,
)
class External(QThread):
# progressChanged = Signal(int)
progressChanged = pyqtSignal(int)
def run(self):
progress = 0
while progress <= 100:
self.progressChanged.emit(progress)
self.msleep(500)
progress += 10
class Window(QWidget):
"""The main application Window."""
def __init__(self):
super().__init__()
self.setWindowTitle("Example")
self.layout = QHBoxLayout()
self.layout.setContentsMargins(6, 6, 6, 6)
self.bar = QProgressBar()
self.bar.setTextVisible(False)
self.bar.setValue(0)
self.layout.addWidget(self.bar)
self.cancel_btn = QPushButton("Cancel")
self.cancel_btn.clicked.connect(self.close)
self.layout.addWidget(self.cancel_btn)
self.ok_btn = QPushButton("OK")
self.ok_btn.clicked.connect(self.run_action)
self.layout.addWidget(self.ok_btn)
self.setLayout(self.layout)
def run_action(self):
self.ok_btn.setEnabled(False)
self.cancel_btn.setEnabled(False)
self.calc = External()
self.calc.progressChanged.connect(self.onProgressChanged)
self.calc.finished.connect(self.onFinished) # +++
self.calc.start()
# self.cancel_btn.setEnabled(True)
# self.ok_btn.setEnabled(True)
# self.bar.setValue(0)
def onProgressChanged(self, value):
self.bar.setValue(value)
def onFinished(self): # +++
self.cancel_btn.setEnabled(True)
self.ok_btn.setEnabled(True)
self.bar.setValue(0)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Hi in my application I have main windows and when a data comes from another thread, I need to show it in another screen for 2 seconds and then go back to previous screen. Screens has many components so I made a simple version to demonstrate my purpose.
Data comes from another thread successfully I can change the text of label. However I can not make disappear the old one and apeear the new one.
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication,QMainWindow, QLabel, QWidget, QGridLayout, QVBoxLayout, QGroupBox
from PyQt5.QtGui import QTextDocument
from PyQt5 import QtCore, Qt
from PyQt5.QtGui import QIcon, QPixmap, QFont
from time import strftime
import datetime
from babel.dates import format_date, format_datetime, format_time
import sys
import worker
import time
class Form(QWidget):
def __init__(self):
super().__init__()
self.label_main = QLabel("Welcome")
self.label_uid = QLabel("Exit")
self.left = 0
self.top = 0
self._width = 480
self._height = 800
self.layout_main = QVBoxLayout()
self.layout_access = QVBoxLayout()
self.obj = worker.Worker() # no parent!
self.thread = QThread() # no parent!
self.obj.return_uid.connect(self.onCardRead)
self.obj.moveToThread(self.thread)
self.obj.finished.connect(self.thread.quit)
self.thread.started.connect(self.obj.get_uid)
self.thread.start()
self.initUI()
def initUI(self):
self.setLayout(self.layout_main)
self.layout_main.addWidget(self.label_main)
self.setWindowTitle('Main Thread')
self.show()
def secondUI(self):
self.setLayout(self.layout_access)
self.layout_access.addWidget(self.label_uid)
self.setWindowTitle('Access Thread')
self.show()
add self.windowname.close() or just self.close() after the show()
Try it:
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class Worker(QtCore.QObject):
return_uid = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
def get_uid(self):
print("start")
count = 0
QtCore.QThread.msleep(1000)
while count < 10:
QtCore.QThread.msleep(200)
self.return_uid.emit(count)
count += 1
self.finished.emit()
class Form(QWidget):
def __init__(self):
super().__init__()
self.label_main = QLabel("Welcome")
self.label_uid = QLabel("Exit")
self.layout_main = QVBoxLayout()
self.layout_access = QVBoxLayout()
# self.obj = worker.Worker() # no parent!
self.obj = Worker()
self.thread = QThread()
self.obj.return_uid.connect(self.onCardRead)
self.obj.moveToThread(self.thread)
# self.obj.finished.connect(self.thread.quit)
self.obj.finished.connect(self.close)
self.thread.started.connect(self.obj.get_uid)
self.thread.start()
self.initUI()
def initUI(self):
self.setLayout(self.layout_main)
self.layout_main.addWidget(self.label_main)
self.setWindowTitle('Main Thread')
self.show()
def secondUI(self):
self.setLayout(self.layout_access)
self.layout_access.addWidget(self.label_uid)
self.setWindowTitle('Access Thread')
self.show()
def onCardRead(self, id):
self.label_main.setNum(id)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Form()
sys.exit(app.exec_())
Update
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class Worker(QtCore.QObject):
return_uid = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
def get_uid(self):
print("start")
count = 0
QtCore.QThread.msleep(1000)
while count < 10:
QtCore.QThread.msleep(500)
self.return_uid.emit(count)
count += 1
self.finished.emit()
class Form(QWidget):
def __init__(self):
super().__init__()
self.label_main = QLabel("Welcome")
self.label_uid = QLabel("Exit")
self.layout_main = QVBoxLayout()
# self.layout_access = QVBoxLayout()
self.obj = Worker()
self.thread = QThread()
self.obj.return_uid.connect(self.onCardRead)
self.obj.moveToThread(self.thread)
# self.obj.finished.connect(self.close)
self.obj.finished.connect(self.secondUI) # <---
self.thread.started.connect(self.obj.get_uid)
self.thread.start()
self.initUI()
def initUI(self):
self.setLayout(self.layout_main)
self.layout_main.addWidget(self.label_main)
self.setWindowTitle('Main Thread')
self.resize(300, 100)
self.show()
def secondUI(self): # <---
self.hide()
self.windowSecond = QWidget()
self.layout_access = QVBoxLayout(self.windowSecond)
self.layout_access.addWidget(self.label_uid)
self.windowSecond.setWindowTitle('Main Screen')
self.windowSecond.resize(300, 200)
self.windowSecond.show()
def onCardRead(self, id):
self.label_main.setNum(id)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Form()
sys.exit(app.exec_())
I want to make a progressbar which runs on a thread and I want to be able to move the widget during the process:
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QPushButton, QLineEdit, QLabel, QComboBox, QProgressBar, QFileDialog
from PyQt4.QtCore import QSize, pyqtSlot, QCoreApplication, SIGNAL, QThread
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
self.setGeometry(500, 300, 820, 350)
self.setWindowTitle("Program")
self.initUI()
def initUI(self):
#Buttons
btnposx = 30
btnposy = 50
self.btn4 = QPushButton('Load', self)
self.btn4.move(btnposx,btnposy+220)
self.connect(self.btn4, SIGNAL("released()"), self.test)
#ProgressBar
self.pb = QProgressBar(self)
self.pb.move(btnposx+150,btnposy+220)
self.pb.resize(470,27)
self.show()
def load(self, val):
self.pb.setValue(val)
def test(self):
self.workThread = WorkThread()
self.connect( self.workThread, SIGNAL('pb_update'), self.load)
self.workThread.start()
class WorkThread(QThread):
def __init__(self):
super(WorkThread, self).__init__()
QThread.__init__(self)
def __del__(self):
self.wait()
#pyqtSlot()
def run(self):
val = 0
l = range(1000000)
for i in l:
if i < len(l):
val += 100/len(l)
self.emit(SIGNAL('pb_update'), val)
return
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())
So far this works, but it does very poorly. The Widget does barely run on my machine, when I try to move it during the process. Is there a way to make this work better so that the Widget doesn't lag or stop responding?
The improvements that your code can have are the following:
Use the new connection style between signals and slots
You must leave a little time for the secondary thread to send the information to the primary thread.
You must indicate the type of connection, in your case Qt::QueuedConnection.
Use pyqtSlot decorator.
You only have to emit the signal when it is necessary, in your case when the whole value of the value changes since the QProgressBar does not recognize floating.
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QPushButton, QLineEdit, QLabel, QComboBox, QProgressBar, QFileDialog
from PyQt4.QtCore import QSize, pyqtSlot, pyqtSignal, QThread, Qt
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
self.setGeometry(500, 300, 820, 350)
self.setWindowTitle("Program")
self.initUI()
def initUI(self):
#Buttons
btnposx = 30
btnposy = 50
self.btn4 = QPushButton('Load', self)
self.btn4.move(btnposx,btnposy+220)
self.btn4.released.connect(self.test)
#ProgressBar
self.pb = QProgressBar(self)
self.pb.move(btnposx+150,btnposy+220)
self.pb.resize(470,27)
self.show()
#pyqtSlot(int)
def load(self, val):
self.pb.setValue(val)
def test(self):
self.workThread = WorkThread()
self.workThread.pb_update.connect(self.load, Qt.QueuedConnection)
#self.workThread.pb_update.connect(self.pb.setValue)
self.workThread.start()
class WorkThread(QThread):
pb_update = pyqtSignal(float)
def __init__(self, *args, **kwargs):
QThread.__init__(self, *args, **kwargs)
self.value = 0
def __del__(self):
self.wait()
#pyqtSlot()
def run(self):
val = 0
l = range(1000000)
for i in l:
if i < len(l):
val += 100/len(l)
int_val = int(val)
if int_val != self.value:
self.value = int_val
self.pb_update.emit(self.value)
QThread.msleep(1)
return
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())