I'm trying to catch the global about to quit signal from my application class which subclasses QApplication. Here is how I attempt to set it up in main.
def cleanUp():
os.system('rosnode kill -a')
sys.exit(0)
## Start Qt event loop
if __name__ == '__main__':
app = Application(sys.argv)
app.aboutToQuit.connect(cleanUp)
app.exec_()
The issue is that this doesn't seem to catch the signal I have apparently connected.
Edit
I'm using PyQtGraph, so preferably is there some way to catch global window closes?
# The main application
class Application(QtGui.QApplication):
def __init__(self, args):
QtGui.QApplication.__init__(self, args)
self.plot = pg.plot(title="UWB")
self.raw_signal = self.plot.plot()
self.filtered_signal = self.plot.plot()
# Start the main loop
self.listen()
You can also override the close event when your QMainWindow is closed. That might be just as useful depending on your use case.
# override exit event
def closeEvent(self, event):
cleanUp()
# close window
event.accept()
Edit: This minimal example works for me; 'closing' is printed when the plot window is closed.
import pyqtgraph as pg
from PyQt4 import QtGui
import sys
# The main application
class Application(QtGui.QApplication):
def __init__(self, args):
QtGui.QApplication.__init__(self, args)
self.plot = pg.plot(title="UWB")
def cleanUp(self):
print 'closing'
## Start Qt event loop
if __name__ == '__main__':
app = Application(sys.argv)
app.aboutToQuit.connect(app.cleanUp)
sys.exit(app.exec_())
Related
I am creating a launcher, in the style of Albert, Alfred or uLauncher. My application runs in the background and shows up when a hotkey is pressed. I use pynput to listen to hotkeys. I cannot use PyQt5 hotkey's feature (can't I?) because I need to listen to keyboard events in the system scope, not only the application's scope.
When the shortcut is pressed, it calls the show() method of my widget. The only issue is that I can't get the focus back on my window, despite the use of raise_, setFocus and activateWindow.
I found a (ugly) workaround that consists in openning a QMessageBox (+ tweaking its appearance to make it invisible, but I didn't put that in the example code) and closing it immediately after.
When I was working on Linux, that workaround was doing the job, and I was ready to forget how ugly it is for it does the job. But I switched to Windows (on which my app must run too), and now this cheeky trick seems to cause freeze then crash of my application. Karma? For sure.
Any ways, my application is useless if it cannot catch focus, so I'm asking two questions, and I'd be happy with only one being solved. :)
Do you know why showing the QMessageBox causes a crash?
Do you know any other way to get the focus back on my application?
Here is an example code to play with.
Thank you very much :)
EDIT: I just found out that even with deactivating the QMessageBox workaround, the application eventually crashes (after 5, 20, 30 calls of the hotkey). So the issue might as well be in the way I bind my shortcut to the GUI, I fear a thread issue, but this is way beyond my knowledge :/
import sys
from PyQt5.QtWidgets import QLineEdit, QApplication, QMessageBox
from PyQt5.QtCore import QSize, Qt, QEvent
from pynput import keyboard
class Launcher(QLineEdit):
def __init__(self):
super().__init__()
self.setFixedSize(QSize(600, 50))
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setPlaceholderText('Search...')
self.installEventFilter(self)
self.set_shortcut('<ctrl>+`')
def set_shortcut(self, shortcut):
def for_canonical(f):
return lambda k: f(listener.canonical(k))
hotkey = keyboard.HotKey(
keyboard.HotKey.parse(shortcut),
self.wake_up)
listener = keyboard.Listener(
on_press=for_canonical(hotkey.press),
on_release=for_canonical(hotkey.release))
listener.start()
def wake_up(self):
print('Waking up')
self.show()
self.cheeky_focus_stealer()
def cheeky_focus_stealer(self):
self.setFocus()
self.raise_()
self.activateWindow()
# Working of linux, but causes freeze/crash on Windows 10
message_box = QMessageBox(self)
message_box.show()
message_box.hide()
def eventFilter(self, obj, event):
if obj is self and event.type() == QEvent.KeyPress:
if event.key() == Qt.Key_Escape:
self.hide()
return True
return super().eventFilter(obj, event)
def main():
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
window = Launcher()
window.show()
app.exec_()
if __name__ == "__main__":
main()
I found my error, so I'm posting an updated piece of code here for it could be helpful to anyone trying to bind a global hotkey to a function that affects a GUI, aka two different thread communicating.
My mistake was indeed to bind the hotkey triggered action straight to my show() method, which implies that the pynput listenner thread will attempt to communnicate with the QApplication.
The trick is to use a pyqtSignal() and to ask it to trigger the show() method. The signal itself being trigger by the hotkey.
After doing that in a clean way, my cheeky_focus_stealer works again, because it is ran from the GUI thread.
import sys
from PyQt5.QtWidgets import QLineEdit, QApplication, QMessageBox
from PyQt5.QtCore import QSize, Qt, QEvent, QObject, pyqtSignal
from pynput import keyboard
class Forwarder(QObject):
signal = pyqtSignal()
class Launcher(QLineEdit):
def __init__(self):
super().__init__()
self.setFixedSize(QSize(600, 50))
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setPlaceholderText('Search...')
self.installEventFilter(self)
self.set_shortcut('<ctrl>+`')
def set_shortcut(self, shortcut):
# The forwarder must be parented to the Launcher
forwarder = Forwarder(parent=self)
forwarder.signal.connect(self.wake_up)
def for_canonical(f):
return lambda k: f(listener.canonical(k))
hotkey = keyboard.HotKey(
keyboard.HotKey.parse(shortcut),
forwarder.signal.emit)
listener = keyboard.Listener(
on_press=for_canonical(hotkey.press),
on_release=for_canonical(hotkey.release))
listener.start()
def wake_up(self):
print('Waking up')
self.show()
self.cheeky_focus_stealer()
def cheeky_focus_stealer(self):
self.setFocus()
self.raise_()
self.activateWindow()
# Working of linux, but causes freeze/crash on Windows 10
message_box = QMessageBox(self)
message_box.show()
message_box.hide()
def eventFilter(self, obj, event):
if obj is self and event.type() == QEvent.KeyPress:
if event.key() == Qt.Key_Escape:
self.hide()
return True
return super().eventFilter(obj, event)
def main():
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
window = Launcher()
window.show()
app.exec_()
if __name__ == "__main__":
main()
I have a small non-GUI application that basically starts an event loop, connects a signal to a slot, and emits a signal. I would like the slot to stop the event loop and exit the application.
However, the application does not exit.
Does anyone have any ideas on how to exit from the event loop?
Python 3.7.0
Qt for Python (PySide2) 5.12.0
import sys
from PySide2 import QtCore, QtWidgets
class ConsoleTest(QtCore.QObject):
all_work_done = QtCore.Signal(str)
def __init__(self, parent=None):
super(ConsoleTest, self).__init__(parent)
self.run_test()
def run_test(self):
self.all_work_done.connect(self.stop_test)
self.all_work_done.emit("foo!")
#QtCore.Slot(str)
def stop_test(self, msg):
print(f"Test is being stopped, message: {msg}")
# neither of the next two lines will exit the application.
QtCore.QCoreApplication.quit()
# QtCore.QCoreApplication.exit(0)
return
if __name__ == "__main__":
app = QtCore.QCoreApplication(sys.argv)
mainwindow = ConsoleTest()
sys.exit(app.exec_())
When you call app.exec_() you are entering the Qt event loop, but since you are executing the code that call quit() at the moment the object is being initialized, your application will never quit.
I can think of two options to achieve the "exit" process you want to do:
Just call sys.exit(1) inside the stop_test() instead of a quit or exit call, or
you use singleShot to quit the application:
import sys
from PySide2 import QtCore, QtWidgets
class ConsoleTest(QtCore.QObject):
all_work_done = QtCore.Signal(str)
def __init__(self, parent=None):
super(ConsoleTest, self).__init__(parent)
self.run_test()
def run_test(self):
self.all_work_done.connect(self.stop_test)
self.all_work_done.emit("foo!")
#QtCore.Slot(str)
def stop_test(self, msg):
print(f"Test is being stopped, message: {msg}")
QtCore.QTimer.singleShot(10, QtCore.qApp.quit)
if __name__ == "__main__":
app = QtCore.QCoreApplication(sys.argv)
mainwindow = ConsoleTest()
sys.exit(app.exec_())
Problem Description
I'm trying to make an application that collects data, processes it, displays it, and some actuation (open/close valves, etc). As a practice for future applications where I have some stricter time constraints, I want to run the data capture and processing in a separate thread.
My current problem is that it's telling me I cannot start a timer from another thread.
Current code progress
import sys
import PyQt5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QThread, pyqtSignal
# This is our window from QtCreator
import mainwindow_auto
#thread to capture the process data
class DataCaptureThread(QThread):
def collectProcessData():
print ("Collecting Process Data")
#declaring the timer
dataCollectionTimer = PyQt5.QtCore.QTimer()
dataCollectionTimer.timeout.connect(collectProcessData)
def __init__(self):
QThread.__init__(self)
def run(self):
self.dataCollectionTimer.start(1000);
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self) # gets defined in the UI file
self.btnStart.clicked.connect(self.pressedStartBtn)
self.btnStop.clicked.connect(self.pressedStopBtn)
def pressedStartBtn(self):
self.lblAction.setText("STARTED")
self.dataCollectionThread = DataCaptureThread()
self.dataCollectionThread.start()
def pressedStopBtn(self):
self.lblAction.setText("STOPPED")
self.dataCollectionThread.terminate()
def main():
# a new app instance
app = QApplication(sys.argv)
form = MainWindow()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Any advice on how to get this to work would be appreciated!
You have to move the QTimer to the DataCaptureThread thread, in addition to that when the run method ends, the thread is eliminated so the timer is eliminated, so you must avoid running that function without blocking other tasks. QEventLoop is used for this:
class DataCaptureThread(QThread):
def collectProcessData(self):
print ("Collecting Process Data")
def __init__(self, *args, **kwargs):
QThread.__init__(self, *args, **kwargs)
self.dataCollectionTimer = QTimer()
self.dataCollectionTimer.moveToThread(self)
self.dataCollectionTimer.timeout.connect(self.collectProcessData)
def run(self):
self.dataCollectionTimer.start(1000)
loop = QEventLoop()
loop.exec_()
I use Qt Designer to build my GUI's and convert them to py files using pyuic5. My end goal here is to interrupt the user from closing the program when a variable == 1 and present them with an 'are you sure you want to close?' type dialog. If said variable == 0 then just close the program normally.
I have seen lots of examples on how to do this, but all of them require editing the code in the GUI module. I import my gui.py file created by pyuic5 into my main script where I do all my connections to buttons, line edits, etc.. I do this so that at anytime I can update the GUI with Qt Designer and not affect the programs functionality.
Is there a way to do this from my main script that has the GUI module from Qt Designer imported?
Example of how my main script is structured:
import philipsControlGui
import sys
def main():
MainWindow.show()
sys.exit(app.exec_())
def test():
print('test')
# Main window setup
app = philipsControlGui.QtWidgets.QApplication(sys.argv)
MainWindow = philipsControlGui.QtWidgets.QMainWindow()
ui = philipsControlGui.Ui_MainWindow()
ui.setupUi(MainWindow)
# Main window bindings
ui.onButton.clicked.connect(test)
### Can I insert something here to do: if user closes the window... do something else instead?
if __name__ == "__main__":
main()
You should create a subclass from your imported gui so you can reimplement the closeEvent method:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from philipsControlGui import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setUpUi(self)
self.ui.onButton.clicked.connect(self.test)
self._check_close = True
def test(self):
print('test')
self._check_close = not self._check_close
def closeEvent(self, event):
if self._check_close:
result = QtWidgets.QMessageBox.question(
self, 'Confirm Close', 'Are you sure you want to close?',
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if result == QtWidgets.QMessageBox.Yes:
event.accept()
else:
event.ignore()
def main():
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If there's a specific 'ExitButton' in your design, you should be able to connect it in the main code and create a pop up dialog. You would have to import the QtCore/QtGui components. I always write my GUI directly (QtDesigner is pain when it comes to these things) so I'm assuming something like this:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
[YOUR CODE]
ui.ExitButton.clicked.connect(Exit)
def Exit():
msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText("Are you sure you want to close this window?")
msg.setWindowTitle("MessageBox demo")
msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
msg.buttonClicked.connect(msgbtn)
retval = msg.exec_()
print "value of pressed message box button:", retval
I am running into an issue when I am taking in values over serial and then attempting to update my Gui with those values. Unfortunately, even though the values update correctly, I am unable to get to the screen to refresh unless I click off of it and then back on to it. I have tried repaint, update, and processEvents() but have been unable to solve the problem.
Here is the code I am working with:
import sys
import serial
import time
import requests
import PyQt5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import*
from PyQt5.QtGui import *
import mainwindow_auto
CUSTOM_EVENT = 1000
ser = serial.Serial('/dev/ttyACM0', 9600)
class TestThread(QThread):
def __init__(self, target):
QThread.__init__(self)
self.target = target
def run(self):
while True:
QApplication.postEvent(self.target, QEvent(QEvent.Type(CUSTOM_EVENT)))
QApplication.processEvents()
QThread.sleep(15)
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)# gets defined in the UI file
self.thread = TestThread(self)
self.thread.start()
def event(s, e):
if(e.type() == CUSTOM_EVENT):
print("Readline: ",int(ser.readline()))
SOC = int(ser.readline())
s.lcdNumber.display(SOC)
s.progressBar.setValue(SOC)
print("SOC: ",SOC)
print(s.lcdNumber.value())
return True
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.lcdNumber.display(30)
form.progressBar.setValue(30)
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Thanks in advance!
Since you already have an I/O thread, let it handle the I/O and sent the received value to the main thread via a signal.
No need for a custom event, no I/O on the main thread.
Just adding a signal to the thread subclass and connecting a slot to that before starting the thread.
Rather than rewriting the code that I had above, I ended up fixing it by force redrawing using s.hide() and s.show() after updating the values in the event code. It forced a redraw that otherwise refused to work.
s.lcdNumber.display(SOC)
s.progressBar.setValue(SOC)
s.hide()
s.show()
As suggested by #KevinKrammer, this is simple to do with a custom signal:
class TestThread(QThread):
serialUpdate = pyqtSignal(int)
def run(self):
while True:
QThread.sleep(1)
value = int(ser.readline())
self.serialUpdate.emit(value)
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
self.thread = TestThread(self)
self.thread.serialUpdate.connect(self.handleSerialUpdate)
self.thread.start()
def handleSerialUpdate(self, value):
print("Readline: ", value)
self.lcdNumber.display(value)
self.progressBar.setValue(value)