I'm using PyQt5.
During MainWindow.__init__ in the code below, I want to initialize some large data (see MainWindow.initData) that takes a long time (20 seconds for example). During that time, I would like to display a progress bar. The progress bar cannot be displayed until after app.exec_() starts executing, thus I cannot call initData from within MainWindow.__init__. So I'm trying to execute initData by posting an event (postEvent) that will cause initData to execute while app.exec_ is executing. I looked at some examples in stackoverflow and arrived at the following code. The event does get posted and initData is called, but the progress bar is not displayed until after initData is completed.
Please advise on how to revise the code so that the progress bar will be displayed while initData is executing. Thanks.
import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import QEvent
class MyEvent(QEvent):
idType = QEvent.registerEventType()
def __init__(self, data):
QEvent.__init__(self, MyEvent.idType)
self.data = data
print("MyEvent.idType ", MyEvent.idType)
def get_data(self):
return self.data
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("My MainWindow")
self.qProgressBar = QtWidgets.QProgressBar(self)
self.qProgressBar.setGeometry(100, 100, 200, 30)
self.label = QtWidgets.QLabel("Hello", self)
self.label.setGeometry(50, 50, 200, 20)
tempEvent = MyEvent("12345")
QtWidgets.QApplication.postEvent(self, tempEvent)
def initData(self):
# To demonstrate, do something that takes a long time (for example
# 20 seconds) and update progress bar.
print("begin initData")
loop = 10000
for i in range(loop):
self.qProgressBar.setValue(int((i+1)/loop*100))
for j in range(loop):
temp = i + j
def customEvent(self, event):
print("customEvent:", event.type())
if (event.type() == MyEvent.idType):
self.label.setText("Received : {0}".format(event.get_data()))
self.initData()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
screen = QtWidgets.QDesktopWidget().screenGeometry()
w.setGeometry(screen.width()//2-200, screen.height()//2-200, 400, 400) # x, y, Width, Height
w.show()
sys.exit(app.exec_())
Thanks musicamante. Here is the final result that works!
import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from time import sleep
class Worker_InitData(QObject):
finished = pyqtSignal()
progress = pyqtSignal(int)
def run(self):
"""Long-running task."""
for i in range(10):
sleep(1)
self.progress.emit(i)
self.finished.emit()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("My MainWindow")
self.qProgressBar = QtWidgets.QProgressBar(self)
self.qProgressBar.setGeometry(100, 100, 200, 30)
self.label = QtWidgets.QLabel("Hello", self)
self.label.setGeometry(50, 50, 200, 20)
self.initData()
def reportProgress(self, i):
self.qProgressBar.setValue(int((i+1)/10*100))
def initData(self):
# Step 2: Create a QThread object
self.thread = QThread()
# Step 3: Create a worker object
self.worker = Worker_InitData()
# Step 4: Move worker to the thread
self.worker.moveToThread(self.thread)
# Step 5: Connect signals and slots
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.worker.progress.connect(self.reportProgress)
# Step 6: Start the thread
self.thread.start()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
screen = QtWidgets.QDesktopWidget().screenGeometry()
w.setGeometry(screen.width()//2-200, screen.height()//2-200, 400, 400) # x, y, Width, Height
w.show()
sys.exit(app.exec_())
Related
This is a basic progress bar.
i just tried this code. the progress bar is loading but not stopping when i push the stop button it just contiues.
and whenever i push the stop button the application is locking until progress bar finish.
import sys
import time
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
TIME_LIMIT = 100
class Actions(QDialog):
"""
Simple dialog that consists of a Progress Bar and a Button.
Clicking on the button results in the start of a timer and
updates the progress bar.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 100)
self.progress.setMaximum(100)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.button2 = QPushButton('Stop', self)
self.button2.move(30, 60)
self.show()
self.show()
self.button.clicked.connect(self.onButtonClick)
self.button2.clicked.connect(self.onButtonClick2)
def onButtonClick(self):
count = 0
while count < TIME_LIMIT:
count += 1
time.sleep(1)
self.progress.setValue(count)
print(count)
stop = 0
if stop == 1:
break
def onButtonClick2(self):
stop = 1
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
In your example, the stop variable in the onButtonClick() method
and the stop variable in the onButtonClick2() method are different local variables.
The while loops and the sleep(1) function block the main interface.
The above task may look something like this:
import sys
from PyQt5.QtWidgets import (QApplication, QDialog, QProgressBar, QPushButton)
from PyQt5.QtCore import QThread, pyqtSignal
class Thread(QThread):
update_signal = pyqtSignal(int)
def __init__(self, *args, **kwargs):
super(Thread, self).__init__(*args, **kwargs)
self.count = 0
self.running = True
def run(self):
while self.running and self.count < 100:
self.count += 1
self.update_signal.emit(self.count)
QThread.msleep(100)
def stop(self):
self.running = False
class Actions(QDialog):
"""
Simple dialog that consists of a Progress Bar and a Button.
Clicking on the button results in the start of a timer and
updates the progress bar.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 100)
self.progress.setMaximum(100)
self.progress.setValue(0)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.button2 = QPushButton('Stop', self)
self.button2.setEnabled(False)
self.button2.move(30, 60)
self.button.clicked.connect(self.onButtonClick)
self.button2.clicked.connect(self.on_stop)
self.thread = Thread()
self.thread.update_signal.connect(self.update)
def onButtonClick(self):
self.button2.setEnabled(True)
self.progress.setValue(0)
self.thread.running = True
self.thread.count = 0
self.thread.start()
self.button.setEnabled(False)
def update(self, val):
self.progress.setValue(val)
if val == 100: self.on_stop()
def on_stop(self):
self.thread.stop()
self.button.setEnabled(True)
self.button2.setEnabled(False)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
window.show()
sys.exit(app.exec_())
Maybe I'm doing this completely wrong here, I'm trying to make the "timeline" for an mp3 player. Everything works and I've tried to set the slider to one value and it was fine. My problem is when I try updating the timeline as it keeps on freezing the program and it doesn't unfreeze. Here's my code, I commented out where to start looking at in terms of where I'm having trouble in:
import sys
from PyQt5.QtCore import QCoreApplication, Qt
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QPushButton,
QSlider
import vlc
player = vlc.MediaPlayer("/songs/Immigrant Song.mp3")
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.setGeometry(50, 50, 400, 200)
self.setWindowTitle('SlugPlayer')
#self.setWindowIcon(QIcon('pic.png'))
self.home()
def home(self):
playing = 0
btn = QPushButton('quit', self)
btn.clicked.connect(self.close_application)
btn.resize(btn.sizeHint()) #set to acceptable size automatic
btn.move(0, 0)
playbtn = QPushButton('Play', self)
# playbtn.clicked.connect(self.update_bar, playing)
playbtn.clicked.connect(self.play_music, playing)
playbtn.resize(playbtn.sizeHint())
playbtn.move(100, 0)
psebtn = QPushButton('Pause', self)
psebtn.clicked.connect(self.pause_music, playing)
psebtn.resize(psebtn.sizeHint())
psebtn.move(200, 0)
stopbtn = QPushButton('Stop', self)
stopbtn.clicked.connect(self.stop_music, playing)
stopbtn.resize(stopbtn.sizeHint())
stopbtn.move(300, 0)
self.sl = QSlider(Qt.Horizontal, self)
self.sl.setFocusPolicy(Qt.NoFocus)
self.sl.setGeometry(30, 50, 300, 20)
self.sl.setMinimum(0)
self.sl.setMaximum(100)
self.sl.setValue(0)
self.sl.move(50, 100)
# self.sl.valueChanged[int].connect(self.print_value)
self.show()
def close_application(self):
sys.exit()
#here is where I'm trying to update the bar
def play_music(self, playing):
player.play()
playing = 1
while playing == 1:
self.update_bar(playing)
def pause_music(self, playing):
player.pause()
playing = 0
def stop_music(self, playing):
player.stop()
playing = 0
def print_value(self, value):
print(value)
#function to update the bar (converts to an int)
def update_bar(self, playing):
self.sl.setValue(int(player.get_position() * 100))
def run():
app = QApplication(sys.argv)
Gui = window()
sys.exit(app.exec_())
run()
An infinite loop like while playing == 1 freezes the GUI since it does not allow you to do other tasks, in these cases it is better to use a QTimer. Going a little further to the bottom of the problem I have implemented a class that is in charge of managing the player by sending signals when necessary.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import vlc
class VLCManager(QtCore.QObject):
durationChanged = QtCore.pyqtSignal(int)
positionChanged = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(VLCManager, self).__init__(parent)
self._player = vlc.MediaPlayer()
self._timer = QtCore.QTimer(self, interval=100, timeout=self.update_values)
def setFileName(self, filename):
self._player = vlc.MediaPlayer(filename)
def position(self):
self.durationChanged.emit(self.duration())
return self._player.get_time()
def setPosition(self, time):
if self.position() != time:
self._player.set_time(time)
#QtCore.pyqtSlot()
def update_values(self):
self.positionChanged.emit(self.position())
def duration(self):
return self._player.get_length()
#QtCore.pyqtSlot()
def play(self):
self._player.play()
self.update_values()
self._timer.start()
#QtCore.pyqtSlot()
def pause(self):
self._player.pause()
self.update_values()
self._timer.stop()
#QtCore.pyqtSlot()
def stop(self):
self._player.stop()
self.update_values()
self._timer.stop()
class window(QtWidgets.QMainWindow):
def __init__(self):
super(window, self).__init__()
self._manager = VLCManager(self)
self._manager.setFileName("/songs/Immigrant Song.mp3")
self.setWindowTitle('SlugPlayer')
self.home()
def home(self):
quitbtn = QtWidgets.QPushButton('quit')
quitbtn.clicked.connect(self.close)
playbtn = QtWidgets.QPushButton('Play')
playbtn.clicked.connect(self._manager.play)
psebtn = QtWidgets.QPushButton('Pause', self)
psebtn.clicked.connect(self._manager.pause)
stopbtn = QtWidgets.QPushButton('Stop', self)
stopbtn.clicked.connect(self._manager.stop)
self.sl = QtWidgets.QSlider(orientation=QtCore.Qt.Horizontal)
self.sl.setFocusPolicy(QtCore.Qt.NoFocus)
self._manager.durationChanged.connect(self.sl.setMaximum)
self._manager.positionChanged.connect(self.sl.setValue)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QVBoxLayout(central_widget)
hlay = QtWidgets.QHBoxLayout()
for b in (quitbtn, playbtn, psebtn, stopbtn, ):
hlay.addWidget(b)
lay.addLayout(hlay)
lay.addWidget(self.sl)
self.sl.valueChanged[int].connect(self._manager.setPosition)
self.show()
def run():
app = QtWidgets.QApplication(sys.argv)
Gui = window()
sys.exit(app.exec_())
run()
I'm trying to write a pyqt5 application with a long running, but not CPU intensive process. I'd like to be able to run it without hanging the UI, so I'm trying to use threading, but since it doesn't seem like I can just run a thread and have it stop after its gone through its code so that it can be run again, I've tried setting up the thread to wait for a variable to change before running.
I know this can't be the correct pattern for running long processes in a pyqt app.
import time
import threading
from PyQt5 import QtWidgets, uic
class MyApp(QtWidgets.QMainWindow):
_run_thread = False
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.ui = uic.loadUi('myapp.ui', self)
self.ui.start_thread_button.clicked.connect(self._run_thread_function)
self._thread = threading.Thread(target=self._run_thread_callback)
self._thread.daemon = True
self._thread.start()
self.ui.show()
def _run_thread_callback(self):
while True:
if self._run_thread:
print("running thread code...")
time.sleep(10)
print("thread code finished")
self._run_thread = False
def _run_thread_function(self):
print("starting thread...")
self._run_thread = True
def main():
app = QtWidgets.QApplication(sys.argv)
MyApp()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Below is a simple demo showing how to start and stop a worker thread, and safely comminucate with the gui thread.
import sys
from PyQt5 import QtCore, QtWidgets
class Worker(QtCore.QThread):
dataSent = QtCore.pyqtSignal(dict)
def __init__(self, parent=None):
super(Worker, self).__init__(parent)
self._stopped = True
self._mutex = QtCore.QMutex()
def stop(self):
self._mutex.lock()
self._stopped = True
self._mutex.unlock()
def run(self):
self._stopped = False
for count in range(10):
if self._stopped:
break
self.sleep(1)
data = {
'message':'running %d [%d]' % (
count, QtCore.QThread.currentThreadId()),
'time': QtCore.QTime.currentTime(),
'items': [1, 2, 3],
}
self.dataSent.emit(data)
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.edit = QtWidgets.QPlainTextEdit()
self.edit.setReadOnly(True)
self.button = QtWidgets.QPushButton('Start')
self.button.clicked.connect(self.handleButton)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.edit)
layout.addWidget(self.button)
self._worker = Worker()
self._worker.started.connect(self.handleThreadStarted)
self._worker.finished.connect(self.handleThreadFinished)
self._worker.dataSent.connect(self.handleDataSent)
def handleThreadStarted(self):
self.edit.clear()
self.button.setText('Stop')
self.edit.appendPlainText('started')
def handleThreadFinished(self):
self.button.setText('Start')
self.edit.appendPlainText('stopped')
def handleDataSent(self, data):
self.edit.appendPlainText('message [%d]' %
QtCore.QThread.currentThreadId())
self.edit.appendPlainText(data['message'])
self.edit.appendPlainText(data['time'].toString())
self.edit.appendPlainText(repr(data['items']))
def handleButton(self):
if self._worker.isRunning():
self._worker.stop()
else:
self._worker.start()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 100, 400, 400)
window.show()
sys.exit(app.exec_())
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.
There will be as many Progress Bars created as there are entities in myList.
Clicking Ok button starts as many sub-processes as there are entities in myList.
Problem:
1. It seems there are way more sub-processes started than entities in myList.
2. Unable to update ProgressBar with a line(that's why it is commented):
pb.update_bar( self.pBars[each]['value'] )
import sys, os
from PyQt4 import QtCore, QtGui
import threading
import time
class PbWidget(QtGui.QProgressBar):
def __init__(self, parent=None, total=20):
super(PbWidget, self).__init__()
self.setMinimum(1)
self.setMaximum(total)
self._active = False
def update_bar(self, to_add_number):
while True:
time.sleep(0.01)
value = self.value() + to_add_number
self.setValue(value)
QtGui.qApp.processEvents()
if (not self._active or value >= self.maximum()):
break
self._active = False
def closeEvent(self, event):
self._active = False
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.myList = ['One','Two','Three','Four','Five','Six','Seven']
self.main_layout = QtGui.QVBoxLayout()
self.pBars={}
for each in self.myList:
pb=PbWidget(total=101)
self.main_layout.addWidget(pb)
self.pBars[each]={'pb':pb, 'name':each, 'value': 0}
ok_button = QtGui.QPushButton("Distribute")
ok_button.clicked.connect(self.OK)
self.main_layout.addWidget(ok_button)
central_widget = QtGui.QWidget()
central_widget.setLayout(self.main_layout)
self.setCentralWidget(central_widget)
def internalFunc(self, myEvent, each):
pb = self.pBars[each]['pb']
state=True
while state:
if self.pBars[each]['value']>=100: state=False
for i in range(12):
self.pBars[each]['value']+=10
print '\n\t ...Working on', each, self.pBars[each]['value'], '\n'
# pb.update_bar( self.pBars[each]['value'] )
time.sleep(0.5)
print "\n\t ProgressBar", each, 'has reached 100%. Its value =', self.pBars[each]['value'], '\n'
def OK(self):
for each in self.pBars:
self.myEvent=threading.Event()
self.c_thread=threading.Thread(target=self.internalFunc, args=(self.myEvent, each,))
self.c_thread.start()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(480, 320)
window.show()
sys.exit(app.exec_())