I'm using pyside but (I think) is a generic Qt question.
I know that QThread implementation calls ._exec() method so we should have an event loop on a started QThread. This way we can use QTimer on that thread (I've done this and it works perfectly). My problem is when QWaitCondition is also used, I'd like to have a "consumer" thread with a infinite loop waiting to be notify (from producers) on the QWaitCondition. The problem I have is that with this design I cannot use QTimer inside the consumer Thread.
This is a snippet of the scenario I'm trying to explain:
from PySide import QtGui
from PySide import QtCore
import sys
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.button = QtGui.QPushButton(self)
self.button.setText("Periodical")
self.button.clicked.connect(self.periodical_call)
self.thread = QtCore.QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.loop)
self.thread.start()
def closeEvent(self, x):
self.worker.stop()
self.thread.quit()
self.thread.wait()
def periodical_call(self):
self.worker.do_stuff("main window") # this works
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.do_stuff) # this also works
self.timer.start(2000)
def do_stuff(self):
self.worker.do_stuff("timer main window")
class Worker(QtCore.QObject):
def do_stuff_timer(self):
do_stuff("timer worker")
def do_stuff(self, origin):
self.origin = origin
self.wait.wakeOne()
def stop(self):
self._exit = True
self.wait.wakeAll()
def loop(self):
self.wait = QtCore.QWaitCondition()
self.mutex = QtCore.QMutex()
self._exit = False
while not self._exit:
self.wait.wait(self.mutex)
print "loop from %s" % (self.origin,)
self.timer = QtCore.QTimer()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.do_stuff_timer)
self.timer.start(1000) # <---- this doesn't work
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
frame = MainWindow()
frame.show()
sys.exit(app.exec_())
Once you click the button we obtain an output like this:
loop from main window
loop from timer main window
loop from timer main window
loop from timer main window
...
This means that the QTimer created inside loop() method is never executed by the event loop.
If I change the design from QWaitCondition to Signals (which is better design imho) the QTimer works, but I'd like to know why they are not working when QWaitCondition is used.
To still process events in a long running task (aka a continuous loop) you need to call QCoreApplication::processEvents().
This will essentially go through all of the queued up slots for your thread.
Calling this function is also necessary for signals (if they are a QueuedConnection signal/slot connection) to make it out of the current thread and into another one.
For PySides, you will need to call PySide.QtCore.QCoreApplication.processEvents()
Your method loop completely occupies thread.
It doesn't return control to event loop. Timer sends its events to event loop which doesn't gain control.
IMO your wile loop is faulty.
One way to fix it is add QApplication.processEvents() in loop (bad approach).
I think you want something else, here are my corrections:
def loop(self):
self.timer = QtCore.QTimer()
self.timer.setSingleShot(False)
self.timer.timeout.connect(self.do_stuff_timer)
self.timer.start(1000)
def stop(self):
self.timer.stop()
this will invoke do_stuff_timer every second until you will call stop.
Related
I'm trying to implement a webcam using PyQt5 from an example I found (here, but not really relevant).
Getting the example to work wasn't an issue, but I wanted to modify some things and I am stuck on one particular problem.
I have two classes, one QObject Capture which has a QBasicTimer that I want to start, and a QWidget MyWidget with a button that is supposed to start the timer of the Capture object, which is inside a QThread.
If I directly connect the button click to the method that starts the timer, everything works fine.
But I want to do some other things when I click the button, so I connected the button to a method of MyWidget first and call the start method of Capture from there. This, however, doesn't work: the timer doesn't start.
Here is a minimal working example:
from PyQt5 import QtCore, QtWidgets
import sys
class Capture(QtCore.QObject):
def __init__(self, parent=None):
super(Capture, self).__init__(parent)
self.m_timer = QtCore.QBasicTimer()
def start(self):
print("capture start called")
self.m_timer.start(1000, self)
def timerEvent(self, event):
print("time event")
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
self.btn_start = QtWidgets.QPushButton("Start")
lay.addWidget(self.btn_start)
self.capture = Capture()
captureThread = QtCore.QThread(self)
captureThread.start()
self.capture.moveToThread(captureThread)
# self.btn_start.clicked.connect(self.capture.start) # this works
self.btn_start.clicked.connect(self.startCapture) # this doesn't
# self.capture.start() # this doesn't either
self.show()
def startCapture(self):
self.capture.start()
def run_app():
app = QtWidgets.QApplication(sys.argv)
mainWin = MyWidget()
mainWin.show()
app.exec_()
run_app()
It is some problem with the QThread, because if I don't use threading it works. I thought maybe it has something to do with the thread not being in some way available when called from a different method than the one it was created in, but calling self.capture.start() directly from the init does not work either.
I only have a very basic grasp of threads. Can someone tell me how I can properly call self.capture.start() from MyWidget and why it works without problems when directly connecting it to the button click?
If you connect the button's clicked signal to the worker's start slot, Qt will automatically detect that it's a cross-thread connection. When the signal is eventually emitted, it will be queued in the receiving thread's event-queue, which ensures the slot will be called within the worker thread.
However, if you connect the button's clicked signal to the startCapture slot, there's no cross-thread connection, because the slot belongs to MyWidget (which lives in the main thread). When the signal is emitted this time, the slot tries to create the timer from within the main thread, which is not supported. Timers must always be started within the thread that creates them (otherwise Qt will print a message like "QBasicTimer::start: Timers cannot be started from another thread").
A better approach is to connect the started and finished signals of the thread to some start and stop slots in the worker, and then call the thread's start and quit methods to control the worker. Here's a demo based on your script, which shows how to implement that:
from PyQt5 import QtCore, QtWidgets
import sys
class Capture(QtCore.QObject):
def __init__(self, parent=None):
super(Capture, self).__init__(parent)
self.m_timer = QtCore.QBasicTimer()
def start(self):
print("capture start called")
self.m_timer.start(1000, self)
def stop(self):
print("capture stop called")
self.m_timer.stop()
def timerEvent(self, event):
print("time event")
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
self.btn_start = QtWidgets.QPushButton("Start")
lay.addWidget(self.btn_start)
self.capture = Capture()
self.captureThread = QtCore.QThread(self)
self.capture.moveToThread(self.captureThread)
self.captureThread.started.connect(self.capture.start)
self.captureThread.finished.connect(self.capture.stop)
self.btn_start.clicked.connect(self.startCapture)
self.show()
def startCapture(self):
if not self.captureThread.isRunning():
self.btn_start.setText('Stop')
self.captureThread.start()
else:
self.btn_start.setText('Start')
self.stopCapture()
def stopCapture(self):
self.captureThread.quit()
self.captureThread.wait()
def closeEvent(self, event):
self.stopCapture()
def run_app():
app = QtWidgets.QApplication(sys.argv)
mainWin = MyWidget()
mainWin.show()
app.exec_()
run_app()
What I'm Doing:
I'm making a PyQt app that allows users to select a script file from their machine, the app then executes it using exec() on a separate QThread then shows them the results. I've already implemented all that, and now I'm trying to add a "Stop Executing" button.
The Problem:
I'm not able to interrupt the script execution, which should happen whenever the user presses the "Stop Executing" button. I can't stop the QObject's task that's executing the script or terminate the QThread that's hosting the object.
My Code:
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QObject, QThread
class Execute(QObject):
def __init__(self, script):
super().__init__()
self.script = script
def run(self):
exec(open(self.script).read())
class GUI(QMainWindow):
# Lots of irrelevant code here ...
# Called when "Start Executing" button is pressed
def startExecuting(self, user_script):
self.thread = QThread()
self.test = Execute(user_script)
self.test.moveToThread(self.thread)
self.thread.started.connect(self.test.run)
self.thread.start()
# Called when "Stop Executing" button is pressed
def stopExecuting(self):
# Somehow stop script execution
My Attempts:
There's a ton of question related to stopping an exec() or a QThread, but none of them work in my case. Here's what I've tried:
Calling thread.quit() from GUI (kills thread after script execution ends - same with wait())
Raising a SystemExit from object (exits the whole app after script execution ends)
Calling thread.terminate() from GUI (app crashes when "Stop Executing" button is pressed)
Using a termination flag variable (not applicable in my case as run() isn't loop based)
So, is there any other solution to stop the exec() or kill the thread right when the button is pressed?
Thank's to #ekhumoro's hint about using multiprocessing instead of multithreading, I was able to find a solution.
I used a QProcess to execute the script, and then called process.kill() when the "Stop Executing" button is clicked. Like so:
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QProcess
class GUI(QMainWindow):
# Lots of irrelevant code here ...
# Called when "Start Executing" button is pressed
def startExecuting(self, user_script):
self.process = QProcess()
self.process.setProcessChannelMode(QProcess.MergedChannels)
self.process.start("python", ["-u", user_script])
# Called when "Stop Executing" button is pressed
def stopExecuting(self):
self.process.kill()
This stops the script execution right away without interrupting the GUI process, which's exactly what I was looking for.
Check next code, maybe it can help you:
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QObject, QThread
from PyQt5 import Qt #+
class WorkThread(Qt.QThread):
threadSignal = Qt.pyqtSignal(int)
def __init__(self):
super().__init__()
def run(self, *args, **kwargs):
c = 0
while True:
Qt.QThread.msleep(100)
c += 1
self.threadSignal.emit(c)
class MsgBox(Qt.QDialog):
def __init__(self):
super().__init__()
layout = Qt.QVBoxLayout(self)
self.label = Qt.QLabel("")
layout.addWidget(self.label)
close_btn = Qt.QPushButton("Close")
layout.addWidget(close_btn)
close_btn.clicked.connect(self.close)
self.setGeometry(900, 65, 400, 80)
self.setWindowTitle('MsgBox from WorkThread')
class GUI(Qt.QWidget): #(QMainWindow):
def __init__(self):
super().__init__()
layout = Qt.QVBoxLayout(self)
self.btn = Qt.QPushButton("Start thread.")
layout.addWidget(self.btn)
self.btn.clicked.connect(self.startExecuting)
self.msg = MsgBox()
self.thread = None
# Lots of irrelevant code here ...
# Called when "Start/Stop Executing" button is pressed
def startExecuting(self, user_script):
if self.thread is None:
self.thread = WorkThread()
self.thread.threadSignal.connect(self.on_threadSignal)
self.thread.start()
self.btn.setText("Stop thread")
else:
self.thread.terminate()
self.thread = None
self.btn.setText("Start thread")
def on_threadSignal(self, value):
self.msg.label.setText(str(value))
if not self.msg.isVisible():
self.msg.show()
if __name__ == '__main__':
app = Qt.QApplication([])
mw = GUI()
mw.show()
app.exec()
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
Let's say I have a PyQt program that goes through a given directory, looks for *JPEG images, and does some processing every time it finds one. Depending on the size of the selected directory, this may take from some seconds to minutes.
I would like to keep my user updated with the status - preferably with something like "x files processed out of y files" . If not, a simple running pulse progress bar by setting progressbar.setRange(0,0) works too.
From my understanding, in order to prevent my GUI from freezing, I will need a seperate thread that process the images, and the original thread that updates the GUI every interval.
But I am wondering if there is any possible way for me to do both in the same thread?
Yes, you can easily do this using processEvents, which is provided for this exact purpose.
I have used this technique for implementing a simple find-in-files dialog box. All you need to do is launch the function that processes the files with a single-shot timer, and then periodically call processEvents in the loop. This is should be good enough to update a counter with the number of files processed, and also allow the user to cancel the process, if necessary.
The only real issue is deciding on how frequently to call processEvents. The more often you call it, the more responsive the GUI will be - but this comes at the cost of considerably slowing the processing of the files. So you may have to experiment a little bit in order to find an acceptable compromise.
UPDATE:
Here's a simple demo that shows how the code could be structured:
import sys, time
from PyQt5 import QtWidgets, QtCore
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.button = QtWidgets.QPushButton('Start')
self.progress = QtWidgets.QLabel('0')
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.progress)
self.button.clicked.connect(self.test)
self._stop = False
self._stopped = True
def test(self):
if self._stopped:
self._stop = False
self.progress.setText('0')
self.button.setText('Stop')
QtCore.QTimer.singleShot(1, self.process)
else:
self._stop = True
def process(self):
self._stopped = False
for index in range(1, 1000):
time.sleep(0.01)
self.progress.setText(str(index))
if not index % 20:
QtWidgets.qApp.processEvents(
QtCore.QEventLoop.AllEvents, 50)
if self._stop:
break
self._stopped = True
self.button.setText('Start')
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I could not achieve the thing you need without multi threading and this is not possible because gui can be only updated in main thread. Below is an algorithm how I did this with multithreading.
Let's say you have your application processing images. Then there are the following threads:
Main thread (that blocks by GUI/QApplication-derived classes.exec())
Timer with, for example, 1 second interval which updates a variable and calls a slot in GUI thread which updates a variable in user interface.
A thread which is processing images on your pc.
def process(self):
self._status = "processing image 1"
....
def _update(self):
self.status_label.setText(self._status)
def start_processing(self, image_path):
# create thread for process and run it
# create thread for updating by using QtCore.QTimer()
# connect qtimer triggered signal to and `self._update()` slot
# connect image processing thread (use connect signal to any slot, in this example I'll stop timer after processing thread finishes)
#pyqtSlot()
def _stop_timer():
self._qtimer.stop()
self._qtimer = None
_update_thread.finished.connect(_stop_timer)
In pyqt5 it is possible to assign a pyqtvariable from a one nested thread(first level). So you can make your variable a pyqtvariable with setter and getter and update gui in a setter or think how you can do this by yourself.
You could just use the python threading module and emit a signal in your threaded routine.
Here's a working example
from PyQt4 import QtGui, QtCore
import threading
import time
class MyWidget(QtGui.QWidget):
valueChanged = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self.computeButton = QtGui.QPushButton("Compute", self)
self.progressBar = QtGui.QProgressBar()
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.computeButton)
layout.addWidget(self.progressBar)
self.computeButton.clicked.connect(self.compute)
self.valueChanged.connect(self.progressBar.setValue)
def compute(self):
nbFiles = 10
self.progressBar.setRange(0, nbFiles)
def inner():
for i in range(1, nbFiles+1):
time.sleep(0.5) # Process Image
self.valueChanged.emit(i) # Notify progress
self.thread = threading.Thread(target = inner)
self.thread.start()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
The following programm should just count up and int and displays its value in a label.
But after a while the GUI stops working, while the loop continous.
from PyQt4 import QtGui,QtCore
import sys
class main_window(QtGui.QWidget):
def __init__(self,parent=None):
#Layout
QtGui.QWidget.__init__(self,parent)
self.bt=QtGui.QPushButton('crash')
self.lbl=QtGui.QLabel('count')
ver=QtGui.QHBoxLayout(self)
ver.addWidget(self.bt)
ver.addWidget(self.lbl)
self.cnt=0
self.running=False
self.connect(self.bt,QtCore.SIGNAL("clicked()"),self.count)
def count(self):
self.running=True
while self.running:
self.cnt+=1
print self.cnt
self.lbl.setText(str(self.cnt))
self.repaint()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mw=main_window()
mw.show()
sys.exit(app.exec_())
Any help?
You're not letting Qt's event loop run, so the GUI is not responding. Also, repaint() is not needed, the QLabel.setText() will repaint the label. All it does is queue up an extra paint event, but this never gets processed.
What you need to do is replace self.repaint() with QtGui.QApplication.processEvents(). This will give the app a chance to process any pending events (including that repaint, as well as ui interaction) while you're in the loop.
def count(self):
self.running=True
while self.running:
self.cnt+=1
print self.cnt
self.lbl.setText(str(self.cnt))
self.repaint()
Have you thought about any exit from this endless loop? E.g. self.running=False.
GUI may stop working because it doesn't have enough time to perform repaint. You may want to add some time.sleep in the loop to wait for the GUI to repaint.
Upd.: You should use QTimer, not a simple while loop, for the behavior you're implementing.
You have to let the main event loop run, something you're not doing.