29

First of all, I'm an experienced C programmer but new to python. I want to create a simple application in python using pyqt. Let's imagine this application it is as simple as when it is run it has to put an icon in the system tray and it has offer an option in its menu to exit the application.

This code works, it shows the menu (I don't connect the exit action and so on to keep it simple)

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()

But this doesn't:

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()

I probably miss something. There are no errors but in the second case when I click with the right button it doesn't show the menu.

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
Nextorlg
  • 794
  • 1
  • 8
  • 16
  • 2
    As a fellow C programmer just getting into Python. I can say "errrmmm" also. – Aiden Bell May 21 '09 at 21:17
  • If your answer solves the issue, please choose the community-wiki copy of your answer as "the" answer (I won't get any reputation :). I also edited your answer to fix a minor typo. – tzot May 22 '09 at 00:37

8 Answers8

32

Well, after some debugging I found the problem. The QMenu object it is destroyed after finish __init__ function because it doesn't have a parent. While the parent of a QSystemTrayIcon can be an object for the QMenu it has to be a Qwidget. This code works (see how QMenu gets the same parent as the QSystemTrayIcon which is an 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()
tzot
  • 92,761
  • 29
  • 141
  • 204
Nextorlg
  • 794
  • 1
  • 8
  • 16
8

I think I would prefer the following as it doesn't seem to depend upon QT's internal garbage collection decisions.

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()
Gerard
  • 89
  • 1
  • 1
8

Here is the code with Exit action implemented

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()
demosthenes
  • 1,151
  • 1
  • 13
  • 17
8

Here is the PyQt5 version (was able to implement the Exit action of demosthenes's answer). Source for porting from PyQt4 to 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)
        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=r''# ADD PATH OF YOUR ICON HERE .png works
    main(on)
danilo
  • 7,680
  • 7
  • 43
  • 46
MagTun
  • 5,619
  • 5
  • 63
  • 104
5

I couldn't get any of the above answers to work in PyQt5 (the exit in the system tray menu, wouldn't actually exit), but i managed to combine them for a solution that does work. I'm still trying to determine if exitAction should be used further somehow.

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)
Silfheed
  • 11,585
  • 11
  • 55
  • 67
Roochiedoor
  • 887
  • 12
  • 19
  • `menu.triggered.connect(self.exit)` wasn't working for me (PyQt5.14.2) but you can remove this line and instead of `exitAction = menu.addAction("Exit") ` you can call the function with: `exitAction = menu.addAction("Exit", self.exit)` – DeemonRider Apr 08 '20 at 13:37
3

With a pyqt5 connected event:

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()
Jonathan
  • 6,741
  • 7
  • 52
  • 69
2

For PySide6: in main.py:

import sys
try:
    from PySide6 import QtWidgets
    from PySide6.QtWidgets import QApplication
    from PySide6.QtGui import (QIcon)
    from PySide6.QtCore import (QSize)
except ImportError as e:
    print("no pyside6")
    print("use python.exe -m pip install pyside6")
    print(str(e))
    exit(0)
from silnik.mytray import SystemTrayIcon
if __name__ == "__main__":
    app = QApplication(sys.argv)
    ico = QIcon()
    ico.addFile(u":/img/brylant_64x64.png", QSize(64, 64))
    ico.addFile(u":/img/brylant_16x16.png", QSize(16, 16))
    ico.addFile(u":/img/brylant_32x32.png", QSize(32, 32))
    ico.addFile(u":/img/brylant_48x48.png", QSize(48, 48))
    ico.addFile(u":/img/brylant_128x128.png", QSize(128, 128))
    app.setWindowIcon(ico)

    trayIcon = silnik.mytray.SystemTrayIcon(ico)
    app.tray = trayIcon
    trayIcon.show()

in folder silnik create mytray.py:

import sys
from PySide6.QtWidgets import (QSystemTrayIcon, QMenu)
from PySide6.QtCore import (QCoreApplication)


class SystemTrayIcon(QSystemTrayIcon):
    def __init__(self, icon):
        super().__init__()
        self.setIcon(icon)
        self.menu = QMenu()
        self.exitAction = self.menu.addAction("Wyjście")
        self.setContextMenu(self.menu)
        self.exitAction.triggered.connect(self.exit)

    def exit(self):
        QCoreApplication.exit()
blackmoon
  • 324
  • 6
  • 17
0

it can be easier

instead:

QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)

write:

super().__init__(self, icon, parent)
blackmoon
  • 324
  • 6
  • 17