I'm writing an application in pyqt4 that displays the time remaining for the user to do something on a main window. I'm using Qt5.5 and pyqt4 with python 2.7.
from PyQt4 import QtCore, QtGui
class MainWin(QtGui.QMainWindow):
def __init__(self):
super(MainWin, self).__init__()
self.initUI()
self.time = 120
self.centralwidget = QtGui.QWidget(self)
self.lcdNumber = QtGui.QLCDNumber(self.centralwidget)
self.lcdNumber.setGeometry(QtCore.QRect(200, 170, 500, 550))
self.displayTime()
self.setCentralWidget(self.centralwidget)
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updateTime)
self.timer.start(1000)
def initUI(self):
self.resize(1100, 850)
# Do other stuff
def displayTime(self):
minute, sec = divmod(self.time,60)
self.lcdNumber.display('{}:{:02d}'.format(minute,sec))
def updateTime(self):
self.time -= 1
self.displayTime()
if self.time == 0:
self.timer.stop()
def main():
import sys
app = QtGui.QApplication(sys.argv)
mw = MainWin()
mw.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This works the way I expected, but when the user click the close button without releasing it, he is able to stop the timer, while he can still see what's on the window. The problem seems to be only on Windows.
According to this Qt Forum thread:
QTimer events not executed when holding minimize/maximize/close button
this is "normal" behaviour on Windows. Basically, pressing and holding any of the title-bar buttons kills all event-processing until the button is released.
It is suggested that a thread is used instead of a timer. But in your case this probably won't help, because you would need to emit a signal to update the GUI, and cross-thread signals also need a running event-loop.
So perhaps you should use a pywin32 timer with a callback, instead.
Related
i'm stucked on a Qtimer that don't start, so the number that i need to update on the GUI is never showed.
i don't want to use a while loop inside the code because i need to change some values in real time without any problem.
i really don't understand why it don't start... or better, it start, but don't run the function update_label.
any suggestion?
Thanks a lot for your time!
here is my code:
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setObjectName("MainWindow")
self.resize(1300, 768)
self.setMinimumSize(1300,768)
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("Bot", "Bot"))
self.number = QtWidgets.QLabel(self)
self.number .setGeometry(QtCore.QRect(1100, 10, 300, 20))
self.number .setObjectName("number")
self.show()
self.threadpool = QThreadPool()
self.updater = Updater()
self.threadpool.start(self.updater)
self.updater.update_progress.latest_number.connect(self.update_number)
#pyqtSlot(str)
def update_number(self, val):
x = str(val)
self.number.setText("Current block: " + x)
class Updater(QRunnable):
def __init__(self):
super(Updater, self).__init__()
self.update_progress = WorkerSignal()
print("started")
def run(self):
self.timer = QTimer()
self.timer.setInterval(2000)
self.timer.timeout.connect(self.update_label)
self.timer.start()
def update_label(self):
provider = configfile.provider
number = str(nmbr.latest_numer(provider))
self.update_progress.latest_number.emit(number)
print("update label started")
After tyring to understand your script, I reached the conclusion that QRunnable is automatically released (or stopped) once Updater.run(self) reaches its end.
But this is supposed to happen, because that's why you should use a QThreadPool in the first place. It is supposed to recycle expired threads in the background of the application.
So, as you're running a QTimer in the thread, QThreadPool thinks that Updater thread is dead, while in reality, the timer is still running on the background of a background thread. So it does its job and releases Updater from the Thread pool.
What you must do in order to keep both the Timer and Updater alive is to manager the Updater thread's lifecycle yourself.
So instead of using QThreadPool and QRunnable, you must go one level lower in terms of abstraction, and use the QThread and QObject themselves in order to control when the thread is going to be stopped.
from PySide2 import QtWidgets, QtCore
from PySide2.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout
from PySide2.QtCore import Qt, QThreadPool, QRunnable, Slot, Signal, QObject, QTimer, QThread
class WorkerSignal(QObject):
# Changed pyqtSignal to Signal
latest_number = Signal(str)
class Worker(QObject):
def __init__(self):
QObject.__init__(self)
self.update_progress = WorkerSignal()
def update_label(self):
print("update label started")
try:
provider = configfile.provider
number = str(nmbr.latest_numer(provider))
self.update_progress.latest_number.emit(number)
except:
print("Error, but just for testing...")
self.update_progress.latest_number.emit("0")
def main(self):
print('Init worker')
self.timer = QTimer()
self.timer.setInterval(2000)
self.timer.timeout.connect(self.update_label)
self.thread().finished.connect(self.timer.stop)
self.timer.start()
class Updater(QThread):
def __init__(self):
QThread.__init__(self)
self.worker = Worker()
# Move worker to another thread, to execute in parallel
self.worker.moveToThread(self)
# Set which method from Worker that should excute on the other thread.
# In this case: Worker.main
self.started.connect(self.worker.main)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
QMainWindow.__init__(self, *args, **kwargs)
self.setObjectName("MainWindow")
self.resize(1300, 768)
self.setMinimumSize(1300,768)
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("SniperBot", "SniperBot"))
self.number = QtWidgets.QLabel(self)
self.number.setGeometry(QtCore.QRect(1100, 10, 300, 20))
self.number.setObjectName("number")
# I did this to see the label, as it was not attached to any
# widget's layout on your script.
widget = QWidget()
layout = QVBoxLayout()
widget.setLayout(layout)
layout.addWidget(self.number)
self.setCentralWidget(widget)
self.scene = widget
self.show()
# Create the Updater thread.
self.updater = Updater()
# Connect the Worker's signal to self.update_number
self.updater.worker.update_progress.latest_number.connect(self.update_number)
# Start the thread
self.updater.start()
def closeEvent(self, evt):
# Before exiting from the application, remember, we control now
# the Updater Thread's lifecycle. So it's our job to stop the thread
# and wait for it to finish properly.
self.updater.quit()
self.updater.wait()
# Accept the event. Exit the application.
evt.accept()
# Changed pyqtSlot to Slot
#Slot(str)
def update_number(self, val):
x = str(val)
print("UPDATE NUMBER: ", x)
self.number.setText("Current block: " + x)
if __name__ == '__main__':
app = QApplication()
win = MainWindow()
win.show()
app.exec_()
What you can do now is follow this pattern and start implementing from here. There are many tutorials that use the moveToThread method. Just be careful to not let the thread or any object be recycled by the python's garbage collector, in order to avoid many other problems.
The script is working, however I use PySide2 instead of PyQt5. So some variable and modules names may change, so just rename them when you try to run the script on your end.
There were a few errors on Worker.update_label(self), which is just a copied script from the old Updater.update_label(self). I don't know what are the variables: configfile or nmbr as they are not initialized on your post. So I made a try-except block to handle the error and make the script work just for testing.
Using PyQt5, I want to implement a two windows displaying one after another automatically, without the user interacting with any window. Something like this:
While True:
Show Window1
wait 2 seconds
Close Window1
Show Window2
wait 2 seconds
Close Window2
The problem I am having is that the main UI thread is stuck in app.exec_() function, so it cannot implement the opening and closing logic.
import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic
class Win1(QMainWindow):
def __init__(self):
super(Win1, self).__init__()
uic.loadUi('win1.ui', self)
self.show()
class Win2(QMainWindow):
def __init__(self):
super(Win2, self).__init__()
uic.loadUi('win2.ui', self)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
while True:
win = Win1()
time.sleep(1)
win.close()
win = Win2()
time.sleep(1)
win.close()
app.exec_() # <--------- Program blocks here
I would appreciate if someone can share a minimal example for this working without blocking. Or please point to the mechanism that should be used.
If you are going to work with Qt then you should forget about sequential logic but you have to implement the logic using events. For example, in your case you want one window to be shown every time T and another to be hidden, so that can be implemented with a QTimer and a flag:
import sys
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5 import uic
class Win1(QMainWindow):
def __init__(self):
super(Win1, self).__init__()
uic.loadUi('win1.ui', self)
self.show()
class Win2(QMainWindow):
def __init__(self):
super(Win2, self).__init__()
uic.loadUi('win2.ui', self)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
timer = QTimer()
timer.setProperty("flag", True)
win1 = Win1()
win2 = Win2()
def on_timeout():
flag = timer.property("flag")
if flag:
win1.show()
win2.close()
else:
win2.show()
win1.close()
timer.setProperty("flag", not flag)
timer.timeout.connect(on_timeout)
timer.start(1 * 1000)
on_timeout()
app.exec_()
You should not use while loop or time.sleep since they block the eventloop in which the GUI lives, that is: they freeze the windows
I'm creating a PyQt app where I want to have a background thread that connects some event handlers and then loops forever until the main window is closed. The problem I am experiencing is that the event handlers I am connecting only work if they are functions defined inside my MainWindow class. I have created a minimal repro below:
import threading
from PyQt5.QtWidgets import QApplication, QDialog, QPushButton, QVBoxLayout
class MainWindow(QDialog):
def __init__(self):
super(MainWindow, self).__init__()
self.button1 = QPushButton("Click Me", self)
self.button2 = QPushButton("Me Too!", self)
layout = QVBoxLayout()
layout.addWidget(self.button1)
layout.addWidget(self.button2)
self.setLayout(layout)
def test(self):
print("test inside class")
def test2():
print("test outside class")
def main(window):
window.button1.clicked.connect(window.test)
window.button2.clicked.connect(test2)
# Loop that runs in thread...
app = QApplication([])
window = MainWindow()
window.show()
threading.Thread(target=main, args=[window]).start()
app.exec_()
When I run this code, the first button prints a message to the console as expected, but the second one does nothing when clicked. If I run the main(window) function in the main thread, then both buttons work. I'm aware that in my small sample program this would be the obvious solution, but for reasons that are complex to explain I need to be able to connect the event handlers from the background thread in my application. Why does connecting a function like test2() that is defined outside the MainWindow class not work when I do it outside of the main thread?
I'm still finding out the reason for the problem but the solution is to indicate the type of connection, in this case Qt::DirectConnection that will make the function test2 run on the same thread of the object that emits the signal (the object that emits the signal is the button that lives in the main thread).
import threading
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QDialog):
def __init__(self):
super(MainWindow, self).__init__()
self.button1 = QtWidgets.QPushButton("Click Me")
self.button2 = QtWidgets.QPushButton("Me Too!")
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button1)
layout.addWidget(self.button2)
#QtCore.pyqtSlot()
def test(self):
print("test inside class")
def test2():
print("test outside class")
def main(window):
window.button1.clicked.connect(window.test)
window.button2.clicked.connect(test2, QtCore.Qt.DirectConnection)
while True:
QtCore.QThread.sleep(1)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
threading.Thread(target=main, args=(window,), daemon=True).start()
sys.exit(app.exec_())
I use KDE on Manjaro linux. I have this script in python to turn off touchpad:
#!/usr/bin/python
import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication
from PyQt5.QtCore import QCoreApplication
from subprocess import call
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
qbtn = QPushButton('On', self)
qbtn.clicked.connect(self.handleButtonOn)
qbtn.resize(qbtn.sizeHint())
qbtn.move(25, 50)
qbtn = QPushButton('Off', self)
qbtn.clicked.connect(self.handleButtonOff)
qbtn.resize(qbtn.sizeHint())
qbtn.move(125, 50)
self.setGeometry(300, 300, 250, 100)
self.setWindowTitle('Touchpad On/Off')
self.show()
def handleButtonOn(self, event):
print ('On')
call(["synclient", "touchpadoff=0"])
call(["notify-send", "Your touchpad is set to ON"])
self.destroy()
def handleButtonOff(self, event):
print ('Off')
call(["synclient", "touchpadoff=1"])
call(["notify-send", "Your touchpad is set to OFF"])
self.destroy()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
It's works almost perfect (turn off my touchpad) but when I start script it's turn off my power button so I can't turn off computer by this button.
There is a problem with Yakuake too, can't write in this terminal. Finally i've start some other terminal for example "konsole" and turn off computer by shutdown command.
I'm new in python. How to make this work OK. I need turn off my touchpad, I use external mouse.
I can't reproduce your issue with power button, but I found out that self.destroy() is causing your script to freeze in some corrupted-not-responding-to-SIGINT state. Replacing self.destroy() with self.close() makes it work on my machine.
Please try replacing self.destroy() with self.close().
I'm trying to make a GUI that used QTimer to create a state machine, but when ever I close the GUI window the timer continues. I think I'm properly making the object that creates my Qtimer a child of the GUI but with the behavior I'm seeing it doesn't seem like it. Here is some code
class Ui_Form(QtGui.QWidget):
def __init__(self):
super(Ui_Form, self).__init__()
self.backEnd = BackEnd(self)
self.backEnd.start()
class BackEnd(QtCore.QObject):
def __init__(self,parent=None):
super(BackEnd,self).__init__(parent)
self.setParent(parent)
self.timer = QtCore.QTimer()
self.timer.setSingleShot(True)
QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.timerHandler)
def timerHandler(self):
print "Im here"
self.timer.start(1000)
def start(self):
self.timer.start(1000)
def stop(self):
self.timer.stop()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
Form = QtGui.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())
Timer does not continue when I close GUI window, it works fine, as desired... anyway, try to override close event for your Ui_Form like this:
def closeEvent(self):
self.backEnd.stop()
I hope that helps.
Also, I've changed your main like this:
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
ui = Ui_Form()
ui.show()
sys.exit(app.exec_())
in your case it might be that Form = QtGui.QWidget() stays alive after you close GUI window. So try that modification first.