PyQt: Mostrar menú en una aplicación de bandeja de sistema

En primer lugar, soy un progtwigdor de C experimentado pero nuevo en Python. Quiero crear una aplicación simple en python usando pyqt. Imaginemos que esta aplicación es tan simple como cuando se ejecuta, tiene que poner un icono en la bandeja del sistema y ofrece una opción en su menú para salir de la aplicación.

Este código funciona, muestra el menú (no conecto la acción de salida, etc. para que sea sencillo)

import sys from PyQt4 import QtGui def main(): app = QtGui.QApplication(sys.argv) trayIcon = QtGui.QSystemTrayIcon(QtGui.QIcon("Bomb.xpm"), app) menu = QtGui.QMenu() exitAction = menu.addAction("Exit") trayIcon.setContextMenu(menu) trayIcon.show() sys.exit(app.exec_()) if __name__ == '__main__': main() 

Pero esto no lo hace:

 import sys from PyQt4 import QtGui class SystemTrayIcon(QtGui.QSystemTrayIcon): def __init__(self, icon, parent=None): QtGui.QSystemTrayIcon.__init__(self, icon, parent) menu = QtGui.QMenu() exitAction = menu.addAction("Exit") self.setContextMenu(menu) def main(): app = QtGui.QApplication(sys.argv) trayIcon = SystemTrayIcon(QtGui.QIcon("Bomb.xpm"), app) trayIcon.show() sys.exit(app.exec_()) if __name__ == '__main__': main() 

Probablemente echo de menos algo. No hay errores, pero en el segundo caso, cuando hago clic con el botón derecho no aparece el menú.

    Bueno, después de un poco de depuración encontré el problema. El objeto QMenu se destruye después de finalizar la función __init__ porque no tiene un padre. Mientras que el padre de un QSystemTrayIcon puede ser un objeto para el QMenu, tiene que ser un Qwidget. Este código funciona (vea cómo QMenu obtiene el mismo padre que QSystemTrayIcon que es un QWidget):

     import sys from PyQt4 import QtGui class SystemTrayIcon(QtGui.QSystemTrayIcon): def __init__(self, icon, parent=None): QtGui.QSystemTrayIcon.__init__(self, icon, parent) menu = QtGui.QMenu(parent) exitAction = menu.addAction("Exit") self.setContextMenu(menu) def main(): app = QtGui.QApplication(sys.argv) w = QtGui.QWidget() trayIcon = SystemTrayIcon(QtGui.QIcon("Bomb.xpm"), w) trayIcon.show() sys.exit(app.exec_()) if __name__ == '__main__': main() 

    Creo que preferiría lo siguiente, ya que no parece depender de las decisiones internas de recolección de basura de QT.

     import sys from PyQt4 import QtGui class SystemTrayIcon(QtGui.QSystemTrayIcon): def __init__(self, icon, parent=None): QtGui.QSystemTrayIcon.__init__(self, icon, parent) self.menu = QtGui.QMenu(parent) exitAction = self.menu.addAction("Exit") self.setContextMenu(self.menu) def main(): app = QtGui.QApplication(sys.argv) style = app.style() icon = QtGui.QIcon(style.standardPixmap(QtGui.QStyle.SP_FileIcon)) trayIcon = SystemTrayIcon(icon) trayIcon.show() sys.exit(app.exec_()) if __name__ == '__main__': main() 

    Aquí está el código con la acción Exit implementada.

     import sys from PyQt4 import QtGui, QtCore class SystemTrayIcon(QtGui.QSystemTrayIcon): def __init__(self, icon, parent=None): QtGui.QSystemTrayIcon.__init__(self, icon, parent) menu = QtGui.QMenu(parent) exitAction = menu.addAction("Exit") self.setContextMenu(menu) QtCore.QObject.connect(exitAction,QtCore.SIGNAL('triggered()'), self.exit) def exit(self): QtCore.QCoreApplication.exit() def main(): app = QtGui.QApplication(sys.argv) w = QtGui.QWidget() trayIcon = SystemTrayIcon(QtGui.QIcon("qtLogo.png"), w) trayIcon.show() sys.exit(app.exec_()) if __name__ == '__main__': main() 

    Aquí está la versión de PyQt5 (fue capaz de implementar la acción Exit de la respuesta de demosthenes). Fuente para portar desde PyQt4 a PyQt5

     import sys from PyQt5 import QtCore, QtGui, QtWidgets # code source: https://stackoverflow.com/questions/893984/pyqt-show-menu-in-a-system-tray-application - add answer PyQt5 #PyQt4 to PyQt5 version: https://stackoverflow.com/questions/20749819/pyqt5-failing-import-of-qtgui class SystemTrayIcon(QtWidgets.QSystemTrayIcon): def __init__(self, icon, parent=None): QtWidgets.QSystemTrayIcon.__init__(self, icon, parent) menu = QtWidgets.QMenu(parent) exitAction = menu.addAction("Exit") self.setContextMenu(menu) def main(image): app = QtWidgets.QApplication(sys.argv) w = QtWidgets.QWidget() trayIcon = SystemTrayIcon(QtGui.QIcon(image), w) trayIcon.show() sys.exit(app.exec_()) if __name__ == '__main__': on=r''# ADD PATH OF YOUR ICON HERE .png works main(on) 

    Con un evento conectado a pyqt5:

     class SystemTrayIcon(QtWidgets.QSystemTrayIcon): def __init__(self, icon, parent=None): QtWidgets.QSystemTrayIcon.__init__(self, icon, parent) menu = QtWidgets.QMenu(parent) exitAction = menu.addAction("Exit") self.setContextMenu(menu) menu.triggered.connect(self.exit) def exit(self): QtCore.QCoreApplication.exit() 

    No pude obtener ninguna de las respuestas anteriores para trabajar en PyQt5 (la salida en el menú de la bandeja del sistema, en realidad no se cerraría), pero logré combinarlas para obtener una solución que funcione. Todavía estoy tratando de determinar si exitAction debería usarse más de alguna manera.

     import sys from PyQt5 import QtWidgets, QtCore, QtGui class SystemTrayIcon(QtWidgets.QSystemTrayIcon): def __init__(self, icon, parent=None): QtWidgets.QSystemTrayIcon.__init__(self, icon, parent) menu = QtWidgets.QMenu(parent) exitAction = menu.addAction("Exit") self.setContextMenu(menu) menu.triggered.connect(self.exit) def exit(self): QtCore.QCoreApplication.exit() def main(image): app = QtWidgets.QApplication(sys.argv) w = QtWidgets.QWidget() trayIcon = SystemTrayIcon(QtGui.QIcon(image), w) trayIcon.show() sys.exit(app.exec_()) if __name__ == '__main__': on='icon.ico' main(on)