So my app has a timer that reminds to do something for a few seconds every few minutes. For example it counts down 10 minutes, then for the next 10 seconds I will stretch and the timer will count 10 seconds. When those 10 seconds are up, it resets to 10 minutes again.
So if the first timer runs out, I'd like it to say "it's time to stretch" and show a windows toast notification. After that second timer of 10 seoconds of stretching is up, I want to show another notification that says "ok you can get back to doing whatever you were doing".
Here is the app:
Here is the code:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5 import QtCore
import sys
from win10toast import ToastNotifier
import itertools
DURATION_INT = 10
toaster = ToastNotifier()
TIME_CYCLER = itertools.cycle([10, 5]) # 10 minutes, 10 seconds
iterToast = itertools.cycle([toaster.show_toast("test1", "test1", duration=3, threaded=True), toaster.show_toast("test2", "test2", duration=3, threaded=True)])
def secs_to_minsec(secs: int):
mins = secs // 60
secs = secs % 60
minsec = f'{mins:02}:{secs:02}'
return minsec
class App(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.time_left_int = DURATION_INT
self.myTimer = QtCore.QTimer(self)
# App window
self.app = QApplication(sys.argv)
self.win = QMainWindow()
self.win.setGeometry(200, 200, 200, 200)
self.win.setWindowTitle("test")
# Widgets
self.titleLabel = QtWidgets.QLabel(self.win)
self.titleLabel.setText("Welcome to my app")
self.titleLabel.move(50,20)
self.timerLabel = QtWidgets.QLabel(self.win)
self.timerLabel.move(50,50)
self.timerLabel.setAlignment(QtCore.Qt.AlignCenter)
self.timerLabel.setStyleSheet("font: 10pt Helvetica")
self.startButton = QtWidgets.QPushButton(self.win)
self.startButton.setText("Start")
self.startButton.move(50,100)
self.startButton.clicked.connect(self.startTimer)
self.stopButton = QtWidgets.QPushButton(self.win)
self.stopButton.setText("Minimize")
self.stopButton.move(50,130)
self.update_gui()
# Show window
self.win.show()
sys.exit(app.exec_())
def startTimer(self):
self.time_left_int = next(TIME_CYCLER)
self.myTimer.timeout.connect(self.timerTimeout)
self.myTimer.start(1000)
def timerTimeout(self):
self.time_left_int -= 1
if self.time_left_int == 0:
next(iterToast)
# toaster.show_toast("test1", "test1", duration=3, threaded=True)
self.time_left_int = next(TIME_CYCLER)
self.update_gui()
def update_gui(self):
minsec = secs_to_minsec(self.time_left_int)
self.timerLabel.setText(minsec)
# def minimize():
# pass
app = QtWidgets.QApplication(sys.argv)
main_window = App()
main_window.show()
sys.exit(app.exec_())
So the problem is that the cycle function doesn't work for me. Whenever I run the app, it just shows the first test1 notification and it repeats every time the clock runs out. It doesn't even cycle through the second notification so I'm thinking itertools might not be what I'm looking for.
Any help would be great :)
Ok so I found the solution. itertools isn't applicable in my case I believe but I created a new variable self.current_timer = 1 inside of my class and changed this function to look like this:
def timerTimeout(self):
self.time_left_int -= 1
if self.time_left_int == 0:
if self.current_timer == 1:
toaster.show_toast("test1", "test1", duration=3, threaded=True)
self.current_timer = 2
elif self.current_timer == 2:
toaster.show_toast("test2", "test2", duration=3, threaded=True)
self.current_timer = 1
self.time_left_int = next(TIME_CYCLER)
self.update_gui()
So eventually, I manually set the variable to be whatever I need it to be for each notificaiton.
Related
I wrote a simple script that pops up a window and starts a countdown timer. It looks like this:
When the timer reachers zero, it resets back to 7 seconds (or whatever I set in that variable). I'd like to start the app with a 30 seconds timer, have it count down to zero, reset to 10 seconds, then to 30 again and so on. The utlimate goal is to remind me to do something every 10 minutes or so, but only for a few seconds. So for example, the app will count down from 10 minutes, reach zero, remind me to stretch for 10 seconds, but when those 10 seconds are up, it will reset to 10 minutes again and so forth, until I close the app.
Here is the code I've got so far:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5 import QtCore
import sys
import time
from win10toast import ToastNotifier
DURATION_INT = 7
toaster = ToastNotifier()
def secs_to_minsec(secs: int):
mins = secs // 60
secs = secs % 60
minsec = f'{mins:02}:{secs:02}'
return minsec
class App(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.time_left_int = DURATION_INT
self.myTimer = QtCore.QTimer(self)
# App window
self.app = QApplication(sys.argv)
self.win = QMainWindow()
self.win.setGeometry(200, 200, 200, 200)
self.win.setWindowTitle("test")
# Widgets
self.titleLabel = QtWidgets.QLabel(self.win)
self.titleLabel.setText("Welcome to my app")
self.titleLabel.move(50,20)
self.timerLabel = QtWidgets.QLabel(self.win)
self.timerLabel.move(50,50)
self.timerLabel.setAlignment(QtCore.Qt.AlignCenter)
self.timerLabel.setStyleSheet("font: 10pt Helvetica")
self.startButton = QtWidgets.QPushButton(self.win)
self.startButton.setText("Start")
self.startButton.move(50,100)
self.startButton.clicked.connect(self.startTimer)
self.stopButton = QtWidgets.QPushButton(self.win)
self.stopButton.setText("Minimize")
self.stopButton.move(50,130)
self.update_gui()
# Show window
self.win.show()
sys.exit(app.exec_())
def startTimer(self):
self.time_left_int = DURATION_INT
self.myTimer.timeout.connect(self.timerTimeout)
self.myTimer.start(1000)
def timerTimeout(self):
self.time_left_int -= 1
if self.time_left_int == 0:
self.time_left_int = DURATION_INT
self.update_gui()
def update_gui(self):
minsec = secs_to_minsec(self.time_left_int)
self.timerLabel.setText(minsec)
# def minimize():
# pass
app = QtWidgets.QApplication(sys.argv)
main_window = App()
main_window.show()
sys.exit(app.exec_())
I'm not so sure how to logically do what I'm trying to do, any help would be appreciated.
Use itertools.cycle to cycle between a series of values indefinitely, then just call next() on it inside your timeout code. Each call gets the next value in the cycle:
import itertools
TIME_CYCLER = itertools.cycle([30, 10]) # 30 seconds, 10 seconds
# your other code here
def startTimer(self):
self.time_left_int = next(TIME_CYCLER)
self.myTimer.timeout.connect(self.timerTimeout)
self.myTimer.start(1000)
def timerTimeout(self):
self.time_left_int -= 1
if self.time_left_int == 0:
self.time_left_int = next(TIME_CYCLER)
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
class MainWindow(QWidget):
signal = pyqtSignal()
def __init__(self):
super(MainWindow, self).__init__()
self.buttonList = list()
self.counter = 0
self.setUI()
self.signal.connect(self.updatebutton)
self.startTimer(10)
def timerEvent(self, *args, **kwargs):
self.signal.emit()
def updatebutton(self):
for button in self.buttonList: # type: QLabel or QPushButton
button.setText(str(self.counter))
self.counter += 1
def setUI(self):
gridLayout = QGridLayout()
for i in range(10):
for j in range(10):
button = QLabel(self)#QPushButton(self)
self.buttonList.append(button)
gridLayout.addWidget(button, i, j)
self.setLayout(gridLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
When I set the timer at 10 ms, all Labels can increase its value by 100 per second. However, when I change the timer to 1 ms, all Labels can not increase its value by 1000 per second.
When I use Pushbutton instead of Label, even the timer is set to 100 ms, all buttons can not increase their value by 100 per second. But when I reduce the number of buttons, e.g., totally 10 buttons instead of 100, these buttons can work well.
3.I meet with this problem recently when I used a data acquisition software called DEWESOFT, which can produce many subwidget or subwindow(very similar to QMidArea and QMidSubwindow) to draw curves and display values of high-frequency signals. Hence, I'd like to find out how and why. I wonder how to control the refresh rate of widget and how to determine the limit of UI refresh ability. If I wanna refresh more widgets at a high frequency, what should I do?
Later I tried Thread to improve, it seems to work a litter better, but I don't know why.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import threading
import sys
import time
class MainWindow(QWidget):
signal = pyqtSignal()
def __init__(self):
super(MainWindow, self).__init__()
self.buttonList = list()
self.counter = 0
self.setUI()
self.signal.connect(self.updatebutton)
self.thread = threading.Thread(target=self.timerevent, args=())
self.thread.setDaemon(True)
self.thread.start()
# self.startTimer(10)
def timerEvent(self, *args, **kwargs):
self.signal.emit()
def timerevent(self):
while True:
self.signal.emit()
time.sleep(0.01)
def updatebutton(self):
print(self.counter)
for button in self.buttonList: # type: QPushButton
button.setText(str(self.counter))
self.counter += 1
def setUI(self):
gridLayout = QGridLayout()
for i in range(10):
for j in range(10):
button = QPushButton(self)
self.buttonList.append(button)
gridLayout.addWidget(button, i, j)
self.setLayout(gridLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
I want to be able to show the progress of a procedure while it is running. My problem is that the QProgressBar seems to be frozen while the procedure is running. Below is an example that shows that while execute_procedure is running (first 5 seconds), the progress_bar doesn't visually update even though the thread is emitting.
from PyQt5 import QtWidgets, QtCore
import sys
import time
class ProgressThread(QtCore.QThread):
progress_changed = QtCore.pyqtSignal(int)
def __init__(self):
super().__init__()
def run(self):
completed = 0
max_time = 15
increment = float(100 / max_time)
while completed < 100:
completed += increment
time.sleep(1)
self.progress_changed.emit(completed)
# print('emitted ', completed) -- this is emitting while
# execute_procedure is running but the progress bar
# is not visually updating until the procedure is completed
class TestProgress(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.tab = QtWidgets.QWidget()
self.tab.setGeometry(300, 300, 1000, 200)
self.button_execute = QtWidgets.QPushButton("Execute", self.tab)
self.button_execute.clicked.connect(self.on_click)
self.progress_bar = QtWidgets.QProgressBar(self.tab)
self.progress_bar.move(0, 40)
self.progress_bar.setProperty("value", 100)
self.worker = ProgressThread()
self.worker.progress_changed.connect(self.update_progress_bar)
self.tab.show()
def on_click(self):
self.worker.start()
self.execute_procedure()
def execute_procedure(self):
# while this procedure is running, the progress bar is not updating
cnt = 0
while cnt <= 5:
cnt += 1
time.sleep(1)
def update_progress_bar(self, value):
self.progress_bar.setValue(value)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = TestProgress()
sys.exit(app.exec_())
You cannot and should not execute a blocking task in the main thread since it prevents the GUI from executing its task correctly.
There are 2 options depending on the use of time.sleep:
If the task of time.sleep() is just to give a delay T seconds then use a QTimer.
# ...
class TestProgress(QtWidgets.QDialog):
# ...
def execute_procedure(self):
self.counter = 0
QtCore.QTimer.singleShot(1000, self.execute_counter)
def execute_counter(self):
if self.counter <= 5:
self.counter += 1
QtCore.QTimer.singleShot(1000, self.execute_counter)
# ...
If in case time.sleep() emulates a task that consumes T seconds then execute it in another thread.
import threading
# ...
class TestProgress(QtWidgets.QDialog):
# ...
def on_click(self):
self.worker.start()
threading.Thread(target=self.execute_procedure, daemon=True).start()
# ...
The application should display an image from a grid of pixels. The color of all pixels should change 30 times per second. After starting the app works for a few seconds and after that the pixels updates will stop. When window resized the pixels updating resumes. With a long-term update of the pixel network, the CPU consumption increases greatly. I tested it on Windows and there the pixel update stops almost immediately. Used the Threading library, and the PyQt5 library to display the interface. How can I make a stable pixels updates in grid?
Here is my code:
from random import choice, randint
from sys import argv
from threading import Thread
from time import sleep
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QIcon, QPalette
from PyQt5.QtWidgets import (QApplication, QFrame, QGridLayout, QMainWindow,
QMenu, QToolBar, QWidget)
class EmulatorWindow(QMainWindow):
spacing = None
app_running = True
def __init__(self, spacing=1, screen_resolution=(16, 16)):
super().__init__()
self.spacing = spacing
# Pixel Grid
self.grid = QGridLayout()
self.grid.setContentsMargins(0, 0, 0, 0)
self.grid.setSpacing(self.spacing)
for x in range(0, screen_resolution[0]):
for y in range(0, screen_resolution[1]):
pixel = QWidget()
pixel.setAutoFillBackground(True)
self.grid.addWidget(pixel, y, x)
# Application thread
self.applicationThread = Thread(target=self.applicationRunner, args=())
self.applicationThread.start()
# Window Properties
self.setGeometry(300, 300, 450, 495)
self.setWindowTitle('Pixels Grid')
widget = QWidget()
widget.setLayout(self.grid)
self.setCentralWidget(widget)
self.setMinimumSize(QSize(450, 495))
self.show()
def applicationRunner(self):
color = 0
while True:
if self.app_running == False:
break
for x in range(0, 16):
for y in range(0, 16):
self.grid.itemAtPosition(x, y).widget().setPalette(QPalette([Qt.red, Qt.blue, Qt.green][color]))
sleep(1 / 30)
color = color + 1
if color == 3:
color = 0
def switchSpacing(self):
self.grid.setSpacing(self.spacing if self.grid.spacing() == 0 else 0)
if __name__ == '__main__':
app = QApplication(argv)
ex = EmulatorWindow()
app.exec_()
ex.app_running = False
Activity Monitor Screenshot
In the screenshot is MenuBar and ToolBar, but, they do not affect the problem
Application Screenshot
The reason why the GUI is not updated with the thread is that Qt prohibits the updating of graphic elements from another thread, for more information read GUI Thread and Worker Thread. A thread should not be used if the task is not heavy, for example if we test when it consumes changing a color using the following code:
t = QtCore.QElapsedTimer()
t.start()
pal = QtGui.QPalette([QtCore.Qt.red, QtCore.Qt.blue, QtCore.Qt.green][self._color])
for x in range(self.grid.rowCount()):
for y in range(self.grid.columnCount()):
w = self.grid.itemAtPosition(x, y).widget()
if w is not None:
w.setPalette(pal)
self._color = (self._color +1) % 3
print(t.elapsed(), " milliseconds")
Obtaining the following results:
4 milliseconds
2 milliseconds
2 milliseconds
3 milliseconds
2 milliseconds
3 milliseconds
3 milliseconds
2 milliseconds
3 milliseconds
# ...
Supporting my statement that it is not a heavy task so in this case you should use a QTimer that allows you to do periodic tasks:
from PyQt5 import QtCore, QtGui, QtWidgets
class EmulatorWindow(QtWidgets.QMainWindow):
def __init__(self, spacing=1, screen_resolution=(16, 16)):
super().__init__()
self.spacing = spacing
# Pixel Grid
self.grid = QtWidgets.QGridLayout()
self.grid.setContentsMargins(0, 0, 0, 0)
self.grid.setSpacing(self.spacing)
for x in range(screen_resolution[0]):
for y in range(screen_resolution[1]):
pixel = QtWidgets.QWidget(autoFillBackground=True)
self.grid.addWidget(pixel, y, x)
# Window Properties
self.setGeometry(300, 300, 450, 495)
self.setWindowTitle('Pixels Grid')
widget = QtWidgets.QWidget()
self.setCentralWidget(widget)
widget.setLayout(self.grid)
self.setMinimumSize(QtCore.QSize(450, 495))
self._color = 0
timer = QtCore.QTimer(self, interval=1000/30, timeout=self.applicationRunner)
timer.start()
#QtCore.pyqtSlot()
def applicationRunner(self):
pal = QtGui.QPalette([QtCore.Qt.red, QtCore.Qt.blue, QtCore.Qt.green][self._color])
for x in range(self.grid.rowCount()):
for y in range(self.grid.columnCount()):
w = self.grid.itemAtPosition(x, y).widget()
if w is not None:
w.setPalette(pal)
self._color = (self._color +1) % 3
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
ex = EmulatorWindow()
ex.show()
sys.exit(app.exec_())
Is their a way possible to update a Qwidget in PyQT4 every 15 minutes ? I know their is something like a Qtimer but is it also possible to make a QWidget update itself at a specific time,for example 00h00 00h15 00h30 00h45 01h00,... . So is their a way to make the Qtimer depend on the time?
The QTimer class has a setInterval method. You can utilize this to change the wait time on the fly. As a short example, this block of code will show the current second. However, if the second is a multiple of 10 it will wait 5 seconds before starting again:
import sys
from PyQt4 import QtGui, QtCore
from time import strftime
class Main(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.initUI()
def initUI(self):
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.Time)
self.timer.start(1000)
self.lcd = QtGui.QLCDNumber(self)
self.lcd.display(strftime("%S"))
self.setCentralWidget(self.lcd)
self.setGeometry(300,300,250,100)
def Time(self):
if int(strftime("%S")) % 10 == 0:
self.timer.setInterval(5000)
else:
self.timer.setInterval(1000)
self.lcd.display(strftime("%S"))
def main():
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
(This was modified slightly from a tutorial showing how to build a digital clock)
It looks like this for 4 consecutive changes of the display:
For your particular problem, when you start the application, you can find how long it is until the next quarter hour via a function like is presented in this answer:
def next_quarter_hour(self):
dt = datetime.datetime.now()
nsecs = dt.minute*60+dt.second+dt.microsecond*1e-6
delta = (nsecs//900)*900+900-nsecs
return delta * 1000.0
This would change the following:
def initUI(self):
...
self.timer.start(next_qt_hour)
...
def Time(self):
if int(strftime("%M")) % 15 == 0:
self.timer.setInterval(900000)
self.lcd.display(strftime("%M"))
Use this to set your first interval. Then once that timer has been exhausted, reset your interval to 15 minutes.