PyQt run time issue - python

I want my code to run by showing the qtwidget and then running the forloop,
but it runs the forloop then shows my widget to me. Why is this?
class tes(QWidget):
def __init__(self):
super(tes, self).__init__()
self.initUI()
for i in range (1000000):
print("s")
def initUI(self):
t = QTableWidget(8,8,self)
self.show()
self.resize(1000,1000)
t.setGeometry(0,0,500,500)
t.show()
def main():
app = QApplication(sys.argv)
t = tes()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

Add QApplication.processEvents() before loop. Your widget will be shown, but unresponsive. To make application responsive, add processEvents() calls to some steps of your loop.
Example:
def __init__(self):
super(tes, self).__init__()
self.initUI()
QApplication.processEvents()
for i in range (1000000):
if not i % 3: # let application process events each 3 steps.
QApplication.processEvents()
print("s")

It's because you run app.exec_() after the for loop executes during the tes object initialization.

The widget is only shown once the application is running, not when its initialised. What exactly are you trying to do in the loop? It might be better to connect it to a signal or handle it in an event, but it all depends what you're trying to acheive.

Related

PyQt5 threading that updates the UI

I'm trying to make a simple Weather app that also shows the current time. However, after multiple trial and error i am asking for help. I've come to the conclusion that i must commit to threading where the clock is continously run in the background to my PyQt UI. Although, it only freezes and crashes, and i can't understand why. As you can understand i've checked multiple posts already on this issue such as, [1] and [2]. But i'm none the wiser...
This is the minimal reproducible code:
class Clock(QObject):
updated_time = pyqtSignal()
def __init__(self, parent=None):
QObject.__init__(self,parent=parent)
def get_time(self):
#This code is meant to be run continously
#For troubleshooting, ive removed the while-loop momentarily
QThread.sleep(1)
now = datetime.now()
current_time = now.strftime("%H:%M")
self.updated_time.emit()
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.initUI()
def initUI(self):
self.setWindowTitle('WeatherApp')
self.resize(700, 500)
self.clock_label = QLabel(self)
self.clock_label.setText('make me change')
self.show()
thread = QThread()
worker = Clock()
worker.moveToThread(thread)
thread.started.connect(lambda: worker.get_time())
worker.updated_time.connect(self.update_clock())
thread.start()
def update_clock(self):
self.clock_label.setText(current_time)
def main():
app = QApplication(sys.argv)
mw = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The current code creates the window with the clock label taking the value of the intially declard variable, current_time. In the background, the function from the worker (do_work) is running in the background, iterating continously with a 1 second delay. As i understand from post 1, i should not update the UI from a thread, but even with the invokeMethod i am unable to achieve any success.. Shouldn't the problem be as easy as to emit a signal from the function do_work back to the App? I've adding one but i receive the error: "AttributeError: 'QThread' object has no attribute ".

Update LCD Number countdown

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

How to display progress without multi-threading

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

QThread: Destroyed while thread is still running

I'm having problem with QThreads in python. I want to change background color of label.
But My application crash while starting.
"QThread: Destroyed while thread is still running"
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
statusTh = statusThread(self)
self.connect(statusTh, SIGNAL('setStatus'), self.st, Qt.QueuedConnection)
statusTh.start()
def st(self):
if self.status == 'ON':
self.ui.label.setStyleSheet('background-color:green')
else:
self.ui.label.setStyleSheet('background-color:red')
class statusThread(QThread):
def __init__(self, mw):
super(statusThread, self).__init__()
def run(self):
while True:
time.sleep(1)
self.emit(SIGNAL('setStatus'))
if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
You're not storing a reference to the thread after it's been created, which means that it will be garbage collected (ie. destroyed) some time after the program leaves MainWindows __init__. You need to store it at least as long as the thread is running, for example use self.statusTh:
self.statusTh = statusThread(self)
self.connect(self.statusTh, SIGNAL('setStatus'), self.st, Qt.QueuedConnection)
self.statusTh.start()
I know it's quite necroposting but this may be useful. In the main section of your script, a first level customized widget need to be stored in variable, not only created.
For example I have a custom widget class called MainWindow that create a QThread. My main is like that:
from myPackage import MainWindow
if __name__ == "__main__":
app = QApplication([])
widget=MainWindow()
sys.exit(app.exec())
if I avoid the widged = definition and only call for MainWindow(), my script will crash with QThread: Destroyed while thread is still running

wxPython Pango error when using a while True loop in a thread

In this program I get an error when I use a while True loop in the thread. Without the loop I get no error. Of course in the real program I don't update a label continuously. Any idea what I'm doing wrong?
This is the program:
import wx
import thread
class Example(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self,parent)
self.InitUI()
def InitUI(self):
self.SetSize((250, 200))
self.Show(True)
self.text = wx.StaticText(self, label='',pos=(20,30))
thread.start_new_thread(self.watch,(self,None))
def watch(self,dummy,e):
while True:
self.text.SetLabel('Closed')
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
And this is the error:
Pango:ERROR:/build/pango1.0-LVHqeM/pango1.0-1.30.0/./pango/pango- layout.c:3801:pango_layout_check_lines: assertion failed: (!layout->log_attrs) Aborted
Any suggestions as to what I'm doing wrong? I'm (obviously) new to threading.
I am not exactly sure if that is what causes you problem, but... You should not interact with the GUI from another thread. You should use wx.CallAfter(). I would consider adding sleep inside the loop also.
wx.CallAfter() documentation says:
Call the specified function after the current and pending event handlers have been completed. This is also good for making GUI method calls from non-GUI threads. Any extra positional or keyword args are passed on to the callable when it is called.
Updated code would than be:
import wx
import thread
import time
class Example(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self,parent)
self.InitUI()
def InitUI(self):
self.SetSize((250, 200))
self.Show(True)
self.text = wx.StaticText(self, label='',pos=(20,30))
thread.start_new_thread(self.watch,(self,None))
def watch(self,dummy,e):
while True:
time.sleep(0.1)
wx.CallAfter(self.text.SetLabel, 'Closed')
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
Maybe you can also consider using wx.Timer.
BTW: Your code runs OK on my PC with Windows 7 and wxPython 2.8.
In addition to the no updates from background threads rule, I've found that in similar situations (high frequency update of UI objects) that it really helps to only update the value if it has changed from what is already displayed. That can greatly reduce the load on the application because if the value does not change then there will be no need for sending and processing paint events, moving pixels to the screen, etc. So in this example I would add a new method that is called via CallAfter that compares the current value in the widget with the requested value, and only calls SetLabel if they are different.

Categories

Resources