PyQt4 threading properly - python

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

Related

PyQt5: Update QMaindow from a QWidget

I use PyQt5 to create an application. I want to update the QMainWinddow after a (time-consuming) function embedded in a QThread, which is started from a QWidget, is finished. How do I address the QMainWindow from QWidget.
Here is a minimal example of the code:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QMainWindow, QVBoxLayout, QPushButton, QLineEdit
from PyQt5.QtCore import QThread, pyqtSignal
class Thread(QThread):
progressFinished = pyqtSignal(int)
def __init__(self, n):
super(Thread, self).__init__()
self.n = n
def run(self):
for i in range(10):
QThread.msleep(100)
function_class = Function()
x = function_class.function(self.n)
self.progressFinished.emit(x)
class Function():
def function(self, n):
return n*5
class Window(QWidget):
def __init__(self):
super().__init__()
layout_window = QVBoxLayout()
self.setLayout(layout_window)
self.button = QPushButton("Open", self)
layout_window.addWidget(self.button)
self.button.clicked.connect(self.open)
self.lineedit = QLineEdit()
layout_window.addWidget(self.lineedit)
def open(self):
self.loading()
def loading(self):
self.thread = Thread(n=int(self.lineedit.text()))
self.thread.progressFinished.connect(self.set_text)
self.thread.finished.connect(self.close)
self.thread.start()
def set_text(self, link):
print(link)
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.window_width, self.window_height = 200, 200
self.setMinimumSize(self.window_width, self.window_height)
self.button = QPushButton("Open", self)
self.button.move(0, 100)
self.button.clicked.connect(self.open)
self.label = QLabel(self)
self.label.setText(str(0))
self.label.move(0, 0)
def open(self):
self.window = Window()
self.window.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
myapp = MyApp()
myapp.show()
try:
sys.exit(app.exec_())
except SystemExit:
print('Closing Window...')
I want to use the def set_text(self, link) function from the Window class to set the text of the QLabel from the MyApp class.

PyQt5: How to show another window/widget for a short time

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

error when close with effect fade window pyqt5

I have a bit when try change new window UI with effect fade. I added effect on closeEvent of mainwindow but it doen't work.
This is my code:
library used:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5 import uic
load ui
uifile_1 = 'home.ui'
form_1, base_1 = uic.loadUiType(uifile_1)
uifile_2 = 'plate.ui'
form_2, base_2 = uic.loadUiType(uifile_2)
Class Home page:
class HomePage(base_1, form_1):
def __init__(self):
super(base_1,self).__init__()
self.setupUi(self)
#add button for click next page
self.btn_start = QPushButton(self)
self.btn_start.clicked.connect(self.change)
self._heightMask = self.height()
self.animation = QPropertyAnimation(self, b"heightPercentage")
self.animation.setDuration(1000)
self.animation.setStartValue(self.height())
self.animation.setEndValue(-1)
self.animation.finished.connect(self.close)
self.isStarted = False
def change(self):
self.plate = PlatePage()
self.plate.show()
self.close()
#pyqtProperty(int)
def heightMask(self):
return self._heightMask
#heightMask.setter
def heightPercentage(self, value):
self._heightMask = value
rect = QRect(0, 0, self.width(), self.heightMask)
self.setMask(QRegion(rect))
def closeEvent(self, event):
if not self.isStarted:
self.animation.start()
self.isStarted = True
event.ignore()
else:
self.closeEvent(self, event)
Class Plate Page
class PlatePage(base_2, form_2):
def __init__(self):
super(base_2, self).__init__()
self.setupUi(self)
self.show()
Please have a look and give me some solution.
Thank You
Try it:
from PyQt5.QtCore import QPropertyAnimation, QThread
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton
class Window(QWidget):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
self.resize(400, 400)
layout = QVBoxLayout(self)
layout.addWidget(QPushButton('Button', self))
self.animation = QPropertyAnimation(self, b'windowOpacity')
self.animation.setDuration(1000)
self.isStarted = False
self.doShow()
def doShow(self):
try:
self.animation.finished.disconnect(self.close)
except:
pass
self.animation.stop()
self.animation.setStartValue(0)
self.animation.setEndValue(1)
self.animation.start()
def closeEvent(self, event):
if not self.isStarted:
self.animation.stop()
self.animation.finished.connect(self.close)
self.animation.setStartValue(1)
self.animation.setEndValue(0)
self.animation.start()
self.isStarted = True
event.ignore()
else:
event.accept()
if __name__ == '__main__':
import sys
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())

pyqt passing signals between classes

I have seen similar questions but none of them seems to work in order to solve my problem.
Basically, my intention is very simple. I try to pass the signal from my settingsWindow class to the centerWindow. Therefore, I created a Qbject called brigde to pass the signal.
In this example, I suppose to get a print "fire" when I press the Ok Button in the settingsWindow. But, nothing is happening.
I'm not sure if I defined all my classes properly. Are all the inheritances correct?
import sys
from PyQt5.QtCore import QFileInfo, QSettings, QCoreApplication, QSize, QRect, Qt, QObject, pyqtSignal
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (qApp, QApplication, QMainWindow, QFormLayout, QPushButton, QTabWidget,QDialog, QWidget, QAction, QVBoxLayout, QHBoxLayout, QSpacerItem, QSizePolicy)
class mainWindow(QMainWindow):
def __init__(self, parent=None):
super(mainWindow, self).__init__(parent)
self.initUI()
def initUI(self):
exitAction = QAction('Exit', self)
exitAction.triggered.connect(qApp.quit)
gSettingAction = QAction('Settings', self)
gSettingAction.triggered.connect(settingsWindow)
self.toolbar = self.addToolBar('Exit')
self.toolbar.setMovable(False)
self.toolbar.addAction(exitAction)
self.toolbar.addAction(gSettingAction)
self.center_window = centerWindow(self)
self.setCentralWidget(self.center_window)
class centerWindow(QWidget):
def __init__(self, parent):
super(centerWindow, self).__init__(parent)
self.initUI()
wb = bridge()
wb.valueUpdated.connect(self.fire)
def initUI(self):
lytLWin = QVBoxLayout()
self.hbox1 = QHBoxLayout()
self.hbox1.addLayout(lytLWin)
self.setLayout(self.hbox1)
def fire(self):
print 'fire'
class settingsWindow(QDialog):
def __init__(self):
QDialog.__init__(self)
self.tab_widget = QTabWidget()
self.win_vbox = QVBoxLayout(self)
self.win_vbox.addWidget(self.tab_widget)
self.tab1 = QWidget()
self.tab_widget.addTab(self.tab1, "Tab_1")
t1 = self.tab1_UI()
self.tab1.setLayout(t1)
self.win_vbox.addLayout(self.btnbar())
self.setLayout(self.win_vbox)
self.wb = bridge()
self.exec_()
def tab1_UI(self):
lytSettings = QFormLayout()
vbox = QVBoxLayout()
vbox.addLayout(lytSettings)
return vbox
def btnbar(self):
ok_set_PB = QPushButton('OK')
ok_set_PB.setObjectName("ok_set_PB_IH")
ok_set_PB.clicked.connect(self.ok_settings)
cancel_set_PB = QPushButton('Cancel')
cancel_set_PB.setObjectName("cancel_set_PB_IH")
btn_hbox = QHBoxLayout()
btn_hbox.addStretch()
btn_hbox.addWidget(ok_set_PB)
btn_hbox.addWidget(cancel_set_PB)
spacerItem = QSpacerItem(2, 2, QSizePolicy.Minimum, QSizePolicy.Minimum)
btn_hbox.addItem(spacerItem)
return btn_hbox
def ok_settings(self):
self.wb.sendSignal()
class bridge(QObject):
valueUpdated = pyqtSignal()
def __init__(self):
QObject.__init__(self)
def sendSignal(self):
self.valueUpdated.emit()
def main():
app = QApplication(sys.argv)
ex = mainWindow()
ex.setGeometry(100,100,1000,600)
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
The problem is that you are working with different instances, so that it works you should change to the following:
class mainWindow(QMainWindow):
def __init__(self, parent=None):
super(mainWindow, self).__init__(parent)
self.wb = bridge()
self.initUI()
def initUI(self):
[...]
gSettingAction.triggered.connect(lambda: settingsWindow(self.wb))
[...]
self.center_window = centerWindow(self.wb, self)
[...]
class centerWindow(QWidget):
def __init__(self, bridge, parent=None):
super(centerWindow, self).__init__(parent)
self.initUI()
wb = bridge
wb.valueUpdated.connect(self.fire)
[...]
class settingsWindow(QDialog):
def __init__(self, bridge, parent=None):
QDialog.__init__(self)
[...]
self.wb = bridge
[...]

Why do external functions cause the PyQt5 window to freeze?

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

Categories

Resources