I am currently starting to use PyQt5 and created my first GUI. Now I would like a program that does the following. When Button 'start' is pressed execute 'function' n-times until Button 'stop' is pressed (imagine a stopwatch) if I press Button 'start' again 'function' gets once again executed and so on.
What I tried so far is the following, that can not work, since we are not able to change a variable from outside the loop (mockup-script)
class Ui(QtWidgets.QDialog):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('interfaceScan.ui', self)
self.startScan = self.findChild(QtWidgets.QPushButton, 'startScan')
self.startScan.clicked.connect(self.startPressed)
self.pauseScan = self.findChild(QtWidgets.QPushButton, 'pauseScan')
self.pauseScan.clicked.connect(self.pausePressed)
self.show()
def startPressed(self):
global pauseScan
pauseScan = False
dosomething()
def pausePressed(self):
global pauseScan
pausScan = True
def dosomething():
while pauseScan == False: #not needed, but the measurement should be executed periodically until 'pause' is pressed
print('Running') #in the reals program the measurement will be executed here
time.sleep(4) #between each measurement the program needs to wait a cirtain amount of time ~1h
app = QtWidgets.QApplication(sys.argv)
window = Ui()
app.exec_()
Any ideas on how I can solve this problem? I am now relatively certain that it does not work when using a while loop, so I am open to suggestions on how to change it up!
The purpose of this script will be to control a measurement setup that should run for let's say 1000 cycles, but I would like to be able to break it in between to change so parameters.
As the OP points out in the comments, the while loop with the time.sleep() only aims to perform a periodic task but this generates a problem since the time.sleep() freezes the GUI, instead a QTimer must be used:
from PyQt5 import QtCore, QtWidgets, uic
class Ui(QtWidgets.QDialog):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi("interfaceScan.ui", self)
self.startScan.clicked.connect(self.startPressed)
self.pauseScan.clicked.connect(self.pausePressed)
self.timer = QtCore.QTimer(self, interval=4 * 1000, timeout=dosomething)
#QtCore.pyqtSlot()
def startPressed(self):
QtCore.QTimer.singleShot(0, dosomething)
self.timer.start()
#QtCore.pyqtSlot()
def pausePressed(self):
self.timer.stop()
def dosomething():
print("Running")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Ui()
window.show()
sys.exit(app.exec_())
Related
I wish to have the QPushButton do different things on different clicks. One the first click it should execute one command and on the next click, it should execute the other command. I've tried to make a program to do it but it only executes one command, not the other
my code I:
import PyQt5.QtWidgets as pyqt
import sys
ongoing = False
class Stuff(pyqt.QWidget):
def __init__(self):
super().__init__()
self.windows()
def windows(self):
w = pyqt.QWidget()
layout = pyqt.QGridLayout()
self.setLayout(layout)
button = pyqt.QPushButton('click me', w)
layout.addWidget(button)
if not ongoing:
button.clicked.connect(click_one)
else:
button.clicked.connect(click_two)
self.show()
w.show()
def click_one():
global ongoing
print('one')
ongoing = not ongoing
def click_two():
global ongoing
print('two')
ongoing = not ongoing
if __name__ == '__main__':
app = pyqt.QApplication(sys.argv)
x = Stuff()
app.exec_()
What should I do to fix this?
Since the value of ongoing is False when the class is initialized, the button's clicked signal gets connected to click_one(). Connect the button to an initial slot and then call the desired function based on the value of ongoing.
class Stuff(pyqt.QWidget):
def __init__(self):
super().__init__()
self.windows()
def windows(self):
w = pyqt.QWidget()
layout = pyqt.QGridLayout()
self.setLayout(layout)
button = pyqt.QPushButton('click me', w)
layout.addWidget(button)
button.clicked.connect(on_click)
self.show()
w.show()
def on_click():
global ongoing
if not ongoing:
click_one()
else:
click_two()
I suggest rewriting the code with the functions and ongoing variable belonging to the class. The QWidget assigned to variable w seems redundant because the QPushButton is then added to the layout of the class, so its parent gets changed anyways.
class Stuff(pyqt.QWidget):
def __init__(self):
super().__init__()
self.ongoing = False
self.windows()
def windows(self):
layout = pyqt.QGridLayout(self)
button = pyqt.QPushButton('click me')
layout.addWidget(button)
button.clicked.connect(self.on_click)
self.show()
def on_click(self):
self.click_one() if not self.ongoing else self.click_two()
self.ongoing = not self.ongoing
def click_one(self):
print('one')
def click_two(self):
print('two')
Also you might be interested in using a checkable button.
I am using this simple code to show warning boxes:
w = QWidget()
result = QMessageBox.warning(w, 'a', x, QMessageBox.Ok)
Is there any way to change MESSAGE dynamically? I want to make a popup which will inform user abut progress of a task that is running in background.
Edit:
Well I tried to do so making this script for testing:
def handleButton(self):
self.msgBox = QMessageBox(self)
self.msgBox.setWindowTitle("Title")
self.msgBox.setIcon(QMessageBox.Warning)
self.msgBox.setText("Start")
self.msgBox.show()
x = 0
for x in range (100):
x = x + 1
print (x)
self.msgBox.setText(str(x))
self.msgBox.show()
time.sleep(1)
The text only shows after finishing the 'for loop', why?
Instead of using a static method you could create an object of the class.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Widget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.msgBox = QMessageBox(self)
self.msgBox.setWindowTitle("Title")
self.msgBox.setIcon(QMessageBox.Warning)
self.msgBox.setText("Start")
self.msgBox.show()
timer = QTimer(self)
timer.timeout.connect(self.onTimeout)
timer.start(1000)
def onTimeout(self):
self.msgBox.setText("datetime: {}".format(QDateTime.currentDateTime().toString()))
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Update:
The problem in your example is the use of time.sleep(). Qt is executed in an eventloop, this eventloop allows you to handle the events of the mouse, keyboard, redraw, etc. but the time.sleep() blocks the eventloop, this you can check trying to change the size of the window, you will see that you can not do it.
Assuming you use time.sleep() to pause, then you must use QEventLoop with QTimer that does not block the Qt eventloop.
def handleButton(self):
self.msgBox = QMessageBox(self)
self.msgBox.setWindowTitle("Title")
self.msgBox.setIcon(QMessageBox.Warning)
self.msgBox.setText("Start")
self.msgBox.show()
for x in range(100):
self.msgBox.setText(str(x+1))
loop = QEventLoop()
QTimer.singleShot(1000, loop.quit)
loop.exec_()
My main objective is to show a constantly evolving value on a Qt-window textEdit. (this window contains only a checkBox and a textEdit).
Sadly, I cannot click on the checkbox and the window is frozen until I shutdown the terminal.
import sys
from threading import Thread
from random import randint
import time
from PyQt4 import QtGui,uic
class MyThread(Thread):
def __init__(self):
Thread.__init__(self)
#function to continually change the targeted value
def run(self):
for i in range(1, 20):
self.a = randint (1, 10)
secondsToSleep = 1
time.sleep(secondsToSleep)
class MyWindow(QtGui.QMainWindow,Thread):
def __init__(self):
Thread.__init__(self)
super(MyWindow,self).__init__()
uic.loadUi('mywindow.ui',self)
self.checkBox.stateChanged.connect(self.checkeven)
self.show()
#i show the value only if the checkbox is checked
def checkeven(self):
while self.checkBox.isChecked():
self.textEdit.setText(str(myThreadOb1.a))
# Run following code when the program starts
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
# Declare objects of MyThread class
myThreadOb1 = MyThread()
myThreadOb2 = MyWindow()
# Start running the threads!
myThreadOb1.start()
myThreadOb2.start()
sys.exit(app.exec_())
At the moment I'm using a thread to set a random value to a, but at the end it is supposed to be a bit more complex as I will have to take the values from an automation.
Do you have any clue why my code is acting like this?
Thank you very much for your help.
The problem is that the while self.checkBox.isChecked() is blocking, preventing the GUI from handling other events.
Also you should not run a PyQt GUI on another thread than the main one.
If you want to send data from one thread to another, a good option is to use the signals.
Doing all these considerations we have the following:
import sys
from threading import Thread
from random import randint
import time
from PyQt4 import QtGui, uic, QtCore
class MyThread(Thread, QtCore.QObject):
aChanged = QtCore.pyqtSignal(int)
def __init__(self):
Thread.__init__(self)
QtCore.QObject.__init__(self)
#function to continually change the targeted value
def run(self):
for i in range(1, 20):
self.aChanged.emit(randint(1, 10))
secondsToSleep = 1
time.sleep(secondsToSleep)
class MyWindow(QtGui.QMainWindow):
def __init__(self):
super(MyWindow,self).__init__()
uic.loadUi('mywindow.ui',self)
self.thread = MyThread()
self.thread.aChanged.connect(self.on_a_changed, QtCore.Qt.QueuedConnection)
self.thread.start()
self.show()
#i show the value only if the checkbox is checked
#QtCore.pyqtSlot(int)
def on_a_changed(self, a):
if self.checkBox.isChecked():
self.textEdit.setText(str(a))
# Run following code when the program starts
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
# Declare objects of MyThread class
w = MyWindow()
sys.exit(app.exec_())
An option more to the style of Qt would be to use QThread since it is a class that handles a thread and is a QObject so it handles the signals easily.
import sys
from random import randint
from PyQt4 import QtGui, uic, QtCore
class MyThread(QtCore.QThread):
aChanged = QtCore.pyqtSignal(int)
def run(self):
for i in range(1, 20):
self.aChanged.emit(randint(1, 10))
secondsToSleep = 1
QtCore.QThread.sleep(secondsToSleep)
class MyWindow(QtGui.QMainWindow):
def __init__(self):
super(MyWindow,self).__init__()
uic.loadUi('mywindow.ui',self)
self.thread = MyThread()
self.thread.aChanged.connect(self.on_a_changed, QtCore.Qt.QueuedConnection)
self.thread.start()
self.show()
#i show the value only if the checkbox is checked
#QtCore.pyqtSlot(int)
def on_a_changed(self, a):
if self.checkBox.isChecked():
self.textEdit.setText(str(a))
# Run following code when the program starts
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
# Declare objects of MyThread class
w = MyWindow()
sys.exit(app.exec_())
I have a problem updating labels in a loop during run time. I think I need to use signals and so on, but I've tried everything I can think of now. What I want my program to do:
When I click the button a loop should start that run some function that take some time. While the function is running the corresponding label should update its text to say "running" and when its done it should say "done" and continue to the next function. I've created some dummy code that represent what I'm after!
I have represented my function with a dummy function that just take some time.
import sys
from PyQt5.QtWidgets import *
import time
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# Set up an example ui
qbtn = QPushButton('Click', self)
qbtn.clicked.connect(self.changeLabels)
qbtn.resize(qbtn.sizeHint())
qbtn.move(100, 50)
# Add labels
self.labels = list()
self.labels.append(QLabel("Lbl1", self))
self.labels[-1].setFixedSize(50, 20)
self.labels.append(QLabel("Lbl2", self))
self.labels[-1].setFixedSize(50, 20)
self.labels[-1].move(0, 20)
self.labels.append(QLabel("Lbl3", self))
self.labels[-1].setFixedSize(50, 20)
self.labels[-1].move(0, 40)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Test')
self.show()
def changeLabels(self):
# Loop over all labels. For each label a function will be executed that will take some time. In this case
# I represent that with a dummy function to just take time. While the function is running the label should say
# "running" and when its finished it should say "done".
for lbl in self.labels:
orgTxt = lbl.text()
lbl.setText("%s Running" % orgTxt)
self.dummyFunction()
lbl.setText("%s Done" % orgTxt)
def dummyFunction(self):
time.sleep(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
The GUI does not support blocking tasks because the GUI needs some time to update some attributes of the window, a possible solution is to use a new thread and implement the dummy function, in the following example the implementation is shown with the use of signals. If your real function updates any GUI view you should not do it directly in the thread, you should do it through signals.
class DummyThread(QThread):
finished = pyqtSignal()
def run(self):
time.sleep(1)
self.finished.emit()
class Example(QWidget):
[...]
def changeLabels(self):
for lbl in self.labels:
orgTxt = lbl.text()
lbl.setText("%s Running" % orgTxt)
thread = DummyThread(self)
thread.start()
thread.finished.connect(lambda txt=orgTxt, lbl=lbl : lbl.setText("%s Done" % txt))
An easier way would be to call QApplication.processEvents() to force QT to process all the events present on the queue. But the UI won't be as responsive as it would be with another thread doing the work and sending signals to the main thread as stated in other answers.
I have a problem with PyQT4 for Python. There is a label with text and button connected to function. The function could change the text of label first, then call other function. There is a problem with it: the function is executed firts, then change text of the label.
Code:
# -*- coding: utf-8 -*-
import time
import sys
from PyQt4 import QtCore, QtGui
def timesleep():
print("start sleep")
time.sleep(5)
print("stop sleep")
class AnyWidget(QtGui.QWidget):
def __init__(self,*args):
QtGui.QWidget.__init__(self,*args)
self.setWindowTitle("PETHARD")
boxlay = QtGui.QHBoxLayout(self)
frame = QtGui.QFrame(self) # Фрейм
frame.setFrameShape(QtGui.QFrame.StyledPanel)
frame.setFrameShadow(QtGui.QFrame.Raised)
gridlay = QtGui.QGridLayout(frame) # Менеджер размещения элементов во фрейме
label = QtGui.QLabel(u"Welcome",frame) # Текстовая метка.
global glabel
glabel = label
gridlay.addWidget(label,0,0)
button1 = QtGui.QPushButton(u"Load From MC", frame)
self.connect(button1, QtCore.SIGNAL("clicked()"), self.ts)
gridlay.addWidget(button1,1,0)
boxlay.addWidget(frame)
def ts(self):
global glabel
glabel.setText(u"Waiting...")
timesleep()
if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
aw = AnyWidget()
aw.show()
sys.exit(app.exec_())
Help me please to fix this problem.
It work's like that because rendering is done later in app. So your glabel.text is changed immediately but you will see changed text on screen after your ts function call, because drawing is done at the end of loop.
If you really want to call your function in new frame (after rendering of new text) then use timer:
timer = QtCore.QTimer()
QtCore.QObject.connect(
timer,
QtCore.SIGNAL("timeout()"),
self.ts
)
timer.start(10)
It should call your function ten milisecond later, so in fact probably after rendering.
You never want to tie-up your GUI with a long-running function. That the label does not update is only one manifestation of the problem. Even if you get the label to update before the function is called, it will still "freeze" your GUI -- no widget will respond to the user until the long-running function completes.
If you have to run such a function, see if there is a way to break it up into small pieces which each relinquish control to the GUI, or consider using a separate thread to run the function:
import time
import sys
from PyQt4 import QtCore, QtGui
class TimeSleep(QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
self.parent = parent
def run(self):
print("start sleep")
time.sleep(5)
print("stop sleep")
self.parent.glabel.setText(u"Done")
class AnyWidget(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
self.setWindowTitle("PETHARD")
boxlay = QtGui.QHBoxLayout(self)
frame = QtGui.QFrame(self) # Фрейм
frame.setFrameShape(QtGui.QFrame.StyledPanel)
frame.setFrameShadow(QtGui.QFrame.Raised)
gridlay = QtGui.QGridLayout(
frame) # Менеджер размещения элементов во фрейме
label = QtGui.QLabel(u"Welcome", frame) # Текстовая метка.
self.glabel = label
gridlay.addWidget(label, 0, 0)
button1 = QtGui.QPushButton(u"Load From MC", frame)
self.connect(button1, QtCore.SIGNAL("clicked()"), self.ts)
gridlay.addWidget(button1, 1, 0)
boxlay.addWidget(frame)
def ts(self):
self.glabel.setText(u"Waiting...")
self.thread = TimeSleep(parent=self)
self.thread.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
aw = AnyWidget()
aw.show()
sys.exit(app.exec_())
Here is a wiki page on how to deal with long-running functions.
The text of the label is being changed, but because you're blocking the main thread you're not giving Qt a chance to paint it.
Use QCoreApplication::processEvents and QCoreApplication::flush:
def ts(self):
glabel.setText(u"Waiting...")
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.flush()
timesleep()