I am building a tiny tool that does file operations at session startup. In order to make sure the user has a visual feedback, I want to associate it with a progress bar.
Here I am so far:
import sys
import time
from PySide.QtGui import *
class ProgressWindowWidget(QWidget):
def __init__(self, parent=None):
super(ProgressWindowWidget, self).__init__()
self.init_ui()
def init_ui(self):
self.setGeometry(500, 500, 600, 100)
self.setWindowTitle('Progress')
self.layout_ = QGridLayout()
self.setLayout(self.layout_)
self.progress_bar = QProgressBar()
self.layout_.addWidget(self.progress_bar, 0, 0, 1, 1)
def my_operations(self):
print('do something 1')
time.sleep(2)
print('do something 2')
time.sleep(2)
print('do something 3')
time.sleep(2)
def main():
app = QApplication(sys.argv)
progress_window = ProgressWindowWidget()
progress_window.show()
progress_window.my_operations()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
My problem is that my_operations is executed first and then my GUI is loaded. I'd like to execute my_operations only when the progress bar is loaded, so I can update it.
According to this, it has something to do with the exec_ main loop, but there's obviously something I don't understand here because I am calling my_operations after show.
Needless to say, I'm a beginner. Does anyone have an idea?
Cheers
Every GUI lives in an event loop that allows you to handle events of the user, the OS, etc. such as the mouse, the keyboard, etc., so if you block this processing the GUI will not update its status, in your case the problem is caused by time.sleep(), which is blocking, preventing the GUI from activating the state of displaying the window. So as a basic rule: do not use time.sleep() inside the main thread of a GUI, I suppose the time.sleep() emulates a task that takes a certain time, for that case you must execute this task from another thread and if you want update the GUI you must do it by means of signals, it should not be done directly.
In the following example I will use threading.Thread() to create a new thread and a signal to update the GUI:
import sys
import time
import threading
from PySide import QtCore, QtGui
class ProgressWindowWidget(QtGui.QWidget):
progressSignal = QtCore.Signal(int)
def __init__(self, parent=None):
super(ProgressWindowWidget, self).__init__()
self.init_ui()
def init_ui(self):
self.setGeometry(500, 500, 600, 100)
self.setWindowTitle('Progress')
self.layout_ = QtGui.QGridLayout()
self.setLayout(self.layout_)
self.progress_bar = QtGui.QProgressBar()
self.progressSignal.connect(self.progress_bar.setValue)
self.layout_.addWidget(self.progress_bar, 0, 0, 1, 1)
def my_operations(self):
print('do something 1')
time.sleep(2)
self.progressSignal.emit(33)
print('do something 2')
time.sleep(2)
self.progressSignal.emit(66)
print('do something 3')
time.sleep(2)
self.progressSignal.emit(100)
def main():
app = QtGui.QApplication(sys.argv)
progress_window = ProgressWindowWidget()
progress_window.show()
t = threading.Thread(target=progress_window.my_operations)
t.start()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Related
from PyQt5 import QtWidgets
import time
def show_message(self):
time.sleep(5)
self.label.setText("It's me")
class Main(QtWidgets.QMainWindow):
def __init__(self):
super(Main, self).__init__()
self.label = QtWidgets.QLabel('Hello', self)
#How to call this func after load application
show_message(self)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication([])
application = Main()
application.show()
sys.exit(app.exec_())
How to call show_message(self) after load application
Does pyqt5 have a function or method like self.afterLoad(application, show_message)
It seems to me something like this available on tkinter
I am guessing you want to start the application and then after 5 seconds the text in the label will get changed to the new text.
What you have right now is almost working, however the problem is that when you call time.sleep(5) then the whole execution of the program will get paused and nothing gets shown for that amount of time. If you still wanna be able to interact with the program during those 5 seconds then you will need to use a timer that is running in the background instead.
PyQt already has something like that in PyQt5.QtCore.QTimer. If you use that then your code could look something like this
from PyQt5 import QtWidgets
from PyQt5.QtCore import QTimer # Importing QTimer
import time
def show_message(self):
self.label.setText("It's me")
# Stopping the timer. Otherwise it will run over and over again.
self.timer.stop()
class Main(QtWidgets.QMainWindow):
def __init__(self):
super(Main, self).__init__()
self.label = QtWidgets.QLabel('Hello', self)
# Create a new QTimer
self.timer = QTimer(self)
# Tell the timer that it should call show_message(self) when the time runs out
self.timer.timeout.connect(lambda: show_message(self))
# Start the timer which then starts running in the background for 5 seconds
self.timer.start(5000)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication([])
application = Main()
application.show()
sys.exit(app.exec_())
One last thing that I would recommend doing is putting your functions that belong to a class inside of it. So here it would probably be better to put show_message inside of the main class because right now you can call the function from every part of the code and that can lead to errors.
Please bear with my question as I am a beginner. I have been having problems implementing the progress bar in pyqt and all of the example I have seen doesn't really explain on how to implement it properly and from this example and this example I somewhat partially made it work but it still hangs. I have this code:
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(750, 450, 400, 200)
self.setFixedSize(self.size())
btn1 = QtGui.QPushButton("Convert", self)
btn1.move(210,171)
btn1.clicked.connect(self.progbar)
def progbar (self):
self.prog_win = QDialog()
self.prog_win.resize(400, 100)
self.prog_win.setFixedSize(self.prog_win.size())
self.prog_win.setWindowTitle("Processing request")
self.lbl = QLabel(self.prog_win)
self.lbl.setText("Please Wait. . .")
self.lbl.move(15,18)
self.progressBar = QtGui.QProgressBar(self.prog_win)
self.progressBar.resize(410, 25)
self.progressBar.move(15, 40)
self.progressBar.setRange(0,1)
self.myLongTask = TaskThread()
#I think this is where I am wrong
#because all of the answers here is very specific
#or just not for beginners
self.prog_win.show()
self.myLongTask.taskFinished.connect(self.onStart)
self.output_settings()
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
def output_convert(self):
#very long process to convert a txt file to excel
#My Thread
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
def run(self):
time.sleep(3)
self.taskFinished.emit()
def run():
app = QtGui.QApplication(sys.argv)
GUI = Window()
app.exec_()
run()
All of the examples and posts here have been very helpful on understanding progress bar implementation but with all the example having specific answers for a specific problem I can't understand the implementation of progress bar in a standard pyqt app. could you guys at least point me in the right direction? Would be appreciated.
This is a very basic progress bar that only uses what is needed at the bare minimum.
It would be wise to read this whole example to the end.
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, 25)
self.progress.setMaximum(100)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
count = 0
while count < TIME_LIMIT:
count += 1
time.sleep(1)
self.progress.setValue(count)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
The progress bar is first imported like so from PyQt5.QtWidgets import QProgressBar
Then it is initialized like any other widget in QtWidgets
The line self.progress.setGeometry(0, 0, 300, 25) method defines the x,y positions on the dialog and width and height of the progress bar.
We then move the button using .move() by 30px downwards so that there will be a gap of 5px between the two widgets.
Here self.progress.setValue(count) is used to update the progress. Setting a maximum value using .setMaximum() will also automatically calculated the values for you. For example, if the maximum value is set as 50 then since TIME_LIMIT is 100 it will hop from 0 to 2 to 4 percent instead of 0 to 1 to 2 every second. You can also set a minimum value using .setMinimum() forcing the progress bar to start from a given value.
Executing this program will produce a GUI similar to this.
As you can see, the GUI will most definitely freeze and be unresponsive until the counter meets the TIME_LIMIT condition. This is because time.sleep causes the OS to believe that program has become stuck in an infinite loop.
QThread
So how do we overcome this issue ? We can use the threading class that PyQt5 provides.
import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
TIME_LIMIT = 100
class External(QThread):
"""
Runs a counter thread.
"""
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < TIME_LIMIT:
count +=1
time.sleep(1)
self.countChanged.emit(count)
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, 25)
self.progress.setMaximum(100)
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_())
Let's break down these modifications.
from PyQt5.QtCore import QThread, pyqtSignal
This line imports Qthread which is a PyQt5 implementation to divide and run some parts(eg: functions, classes) of a program in the background(also know as multi-threading). These parts are also called threads. All PyQt5 programs by default have a main thread and the others(worker threads) are used to offload extra time consuming and process intensive tasks into the background while still keeping the main program functioning.
The second import pyqtSignal is used to send data(signals) between worker and main threads. In this instance we will be using it to tell the main thread to update the progress bar.
Now we have moved the while loop for the counter into a separate class called External.
class External(QThread):
"""
Runs a counter thread.
"""
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < TIME_LIMIT:
count +=1
time.sleep(1)
self.countChanged.emit(count)
By sub-classing QThread we are essentially converting External into a class that can be run in a separate thread. Threads can also be started or stopped at any time adding to it's benefits.
Here countChanged is the current progress and pyqtSignal(int) tells the worker thread that signal being sent is of type int. While, self.countChanged.emit(count) simply sends the signal to any connections in the main thread(normally it can used to communicate with other worker threads as well).
def onButtonClick(self):
self.calc = External()
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
def onCountChanged(self, value):
self.progress.setValue(value)
When the button is clicked the self.onButtonClick will run and also start the thread. The thread is started with .start(). It should also be noted that we connected the signal self.calc.countChanged we created earlier to the method used to update the progress bar value. Every time External::run::count is updated the int value is also sent to onCountChanged.
This is how the GUI could look after making these changes.
It should also feel much more responsive and will not freeze.
The answer to my own question. It is not that hard if you can understand the concept of threading and passing variables through classes. My first mistake was really the lack of knowledge about Worker threads, second is I thought that once you declare the Thread it means that you just have to call it so that it would run the function inside the main class so I was searching on how you would implement that and all I thought was wrong.
Solution
All the hard/Long processes SHOULD be in the subclassed QThread under def run and should be called in your class Window(QtGui.QMainWindow): or main loop and this is what my code look like now
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(750, 450, 400, 200)
self.setFixedSize(self.size())
btn1 = QtGui.QPushButton("Convert", self)
btn1.move(210,171)
btn1.clicked.connect(self.progbar)
def progbar (self):
self.prog_win = QDialog()
self.prog_win.resize(400, 100)
self.prog_win.setFixedSize(self.prog_win.size())
self.prog_win.setWindowTitle("Processing request")
self.lbl = QLabel(self.prog_win)
self.lbl.setText("Please Wait. . .")
self.lbl.move(15,18)
self.progressBar = QtGui.QProgressBar(self.prog_win)
self.progressBar.resize(410, 25)
self.progressBar.move(15, 40)
self.progressBar.setRange(0,1)
self.myLongTask = TaskThread(var = DataYouWantToPass) #initializing and passing data to QThread
self.prog_win.show()
self.onStart() #Start your very very long computation/process
self.myLongTask.taskFinished.connect(self.onFinished) #this won't be read until QThread send a signal i think
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
#added this function to close the progress bar
def onFinished(self):
self.progressBar.setRange(0,1)
self.prog_win.close()
#My Thread
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
#I also added this so that I can pass data between classes
def __init__(self, var, parent=None):
QThread.__init__(self, parent)
self.var = var
def run(self):
#very long process to convert a txt file to excel
def run():
app = QtGui.QApplication(sys.argv)
GUI = Window()
app.exec_()
run()
If something in this answer is wrong then please correct me as it would be a great help to understand it more or maybe some dos and don't
I am creating an alarm clock in python using PyQt4 and in that I am using LCD display widget, which display current updating time. For that I am using threading. But I am new to it so the problem is I have no clue how to debug that thing.
This is my code
import sys
from PyQt4 import QtGui, uic
import time
import os
from threading import Thread
class MyWindow(QtGui.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
uic.loadUi('AlarmClock_UI.ui', self)
self.show()
self.comboBox.setCurrentIndex(0)
self.comboBox.currentIndexChanged.connect(self.getSelection)
self.lineEdit.setText('Please select the reminder type')
timeThread = Thread(target = self.showTime())
timeThread.start()
def getSelection(self):
if self.comboBox.currentIndex() == 1:
self.lineEdit.setText('Select the alarm time of your choice')
elif self.comboBox.currentIndex() == 2:
self.lineEdit.setText('Use those dials to adjust hour and minutes')
else:
self.lineEdit.setText('Please select the reminder type')
def showTime(self):
showTime = time.strftime('%H:%M:%S')
self.lcdNumber.display(showTime)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MyWindow()
sys.exit(app.exec_())
I tried while loop in showTime() function then it was not even loading GUI just running in the background.
Thanks :)
As has been said elsewhere, you do not need to use threading for this, as a simple timer will do. Here is a basic demo script:
import sys
from PyQt4 import QtCore, QtGui
class Clock(QtGui.QLCDNumber):
def __init__(self):
super(Clock, self).__init__(8)
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.showTime)
self.timer.start(1000)
self.showTime()
def showTime(self):
time = QtCore.QTime.currentTime()
self.display(time.toString('hh:mm:ss'))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Clock()
window.setWindowTitle('Clock')
window.setGeometry(500, 100, 400, 100)
window.show()
sys.exit(app.exec_())
Qt does not support doing GUI operations in threads other than the main thread. So when you call self.lcddisplay.display(showTime) from within the context of your spawned thread, that is an error and Qt will not work correctly.
As tdelaney suggested in his comment, the best way to handle this sort of thing is to use a QTimer to emit a signal at the appropriate intervals, and update your lcddisplay in the slot that signal is connected to.
(if you insist on using threads, however, e.g. as a learning exercise, then your spawned thread would need to send a message to the main thread to tell the main thread to do the display update, rather than trying to do the update itself)
I'm very new to Python and I have made a very simple countdown timer. The GUI was created in Qt Designer. There is a spin box for input of seconds, a start button and an LCD number counter. The counter counts down fine using the code below:
def start_btn_clicked(self):
x = self.Minute_spinBox.value()
for i in xrange(x,0,-1):
time.sleep(1)
print (i)
So that I could see what was happening as I played around with it, I added the print instruction so that it shows the countdown in the Python console as it runs. I then thought I could maybe quite easily have the LCD number display the countdown with something like:
self.lcdNumber.display(i)("%SS")
But no matter what I try, I cant get it to show. With the line above, I get the first number displayed, but then I get an error saying:
self.lcdNumber.display(i)("%SS")
TypeError: 'NoneType' object is not callable
I have tried so many variations that I no longer know where I started and here was me thinking it would be simple. I'd love to know why I cant get it to display the countdown.
Just adding one line of code to my original code will allow the lcdNumber to display the count...
def start_btn_clicked(self):
x = self.Minute_spinBox.value()
for i in xrange(x,0,-1):
time.sleep(1)
app.processEvents() # just this one line allows display of 'i'
self.lcdNumber.display(i)`
And works perfectly
The display function returns None, so doing None("%SS") obviously isn't allowed.
self.lcdNumber.display(i) is enough to show the countdown!
To let Qt paint the widgets while looping run the countdown from another thread. See an example.
import time
from threading import Thread
from PyQt4.QtGui import QApplication, QMainWindow, QLCDNumber
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.lcdnumber = QLCDNumber(self)
self.resize(400, 400)
t = Thread(target=self._countdown)
t.start()
def _countdown(self):
x = 10
for i in xrange(x,0,-1):
time.sleep(1)
self.lcdnumber.display(i)
if __name__ == "__main__":
app = QApplication([])
window = Window()
window.show()
app.exec_()
The for loop is blocking the GUI.
The slot connected to the button's clicked signal is processed synchronously. This means the event-loop must wait for the slot to return before it can process any more events (including the paint events needed for updating the GUI).
So you need to find a way to process these events whilst the for loop is running. There are various ways of doing this, such as using a QTimer or a QThread. But the simplest way of fixing your particular example would be to use QCoreApplication.processEvents.
Here's an example that shows how to do that:
import sys, time
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.spinbox = QtGui.QSpinBox(self)
self.spinbox.setValue(5)
self.lcdnumber = QtGui.QLCDNumber(self)
self.button = QtGui.QPushButton('Start', self)
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.spinbox)
layout.addWidget(self.lcdnumber)
layout.addWidget(self.button)
def handleButton(self):
for tick in range(self.spinbox.value(), -1, -1):
self.lcdnumber.display(tick)
self.button.setEnabled(not tick)
# continually process events for one second
start = time.time()
while time.time() - start < 1:
QtGui.qApp.processEvents()
time.sleep(0.02)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 300, 200)
window.show()
sys.exit(app.exec_())
I have a small application, that requires login before it starts.
But if user rejects login(press cancel button), application won't close, it's just freeze.
Here is the simplified code:
import sys
from PyQt5 import QtWidgets, QtCore
class LoginWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super(LoginWindow, self).__init__(parent)
self.resize(250, 150)
self.move(500, 500)
self.setWindowTitle('Login')
self.login_input = QtWidgets.QLineEdit(self)
self.login_input.move(10, 10)
self.password_input = QtWidgets.QLineEdit(self)
self.password_input.move(10, 50)
self.password_input.setEchoMode(QtWidgets.QLineEdit.Password)
self.button_box = QtWidgets.QDialogButtonBox(self)
self.button_box.move(10, 80)
self.button_box.setOrientation(QtCore.Qt.Horizontal)
self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel |
QtWidgets.QDialogButtonBox.Ok)
self.button_box.accepted.connect(self.login)
self.button_box.rejected.connect(self.reject)
def login(self):
self.accept()
def cancel(self):
self.reject()
class MainWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(250, 150)
self.move(500, 500)
self.setWindowTitle('Main')
self.input = QtWidgets.QLineEdit(self)
self.input.move(10, 10)
self.show()
def main():
app = QtWidgets.QApplication([])
l = LoginWindow()
l.show()
login_result = l.exec_()
print(login_result)
if login_result == QtWidgets.QDialog.Accepted:
m = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
What am I doing wrong?
I use python 3 and PyQt5
This happens because PySide has not processed any of it's events.
app.exec_()
This starts the main event loop that continually processes every GUI interaction. This should be called before you execute any GUI code, so the events can be processed correctly from the event Queue.
The QDialog.exec_() is a blocking operation preventing the code from continuing until it gets a response.
If you want to see the dialog items then you may be able to get around this.
QtGui.QApplication.processEvents()
This processes all of the events in the event Queue, so you would probably have to keep calling this method.
Also after you initialize your main window you will have to show the main window.
I find a way to avoid this bug:
I've changed main function:
def main():
app = QtWidgets.QApplication([])
if LoginWindow().exec_() == QtWidgets.QDialog.Accepted:
m = MainWindow()
sys.exit(app.exec_())
And it works normal, but is still can't understand, what was the root cause of the problem