PyQt: Show menu in a system tray application
Asked Answered
S

9

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.

Sarver answered 21/5, 2009 at 17:5 Comment(2)
As a fellow C programmer just getting into Python. I can say "errrmmm" also.Peatroy
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.Pleione
S
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()
Sarver answered 21/5, 2009 at 23:8 Comment(0)
A
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()
Alva answered 12/7, 2010 at 17:32 Comment(1)
Wow, so this is not the systray at all?Alurta
T
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()
Tudor answered 27/10, 2016 at 13:49 Comment(1)
Note*: You must have qtLogo.png image to the same directory with the scriptTudor
C
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://mcmap.net/q/373110/-pyqt-show-menu-in-a-system-tray-application  - add answer PyQt5
#PyQt4 to PyQt5 version: https://mcmap.net/q/375873/-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)
Ceramist answered 22/1, 2017 at 11:38 Comment(1)
you forgot the event in exit: menu.triggered.connect(self.exit)Macymad
C
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)
Caudal answered 17/4, 2018 at 2:9 Comment(1)
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)Hexagonal
E
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()
Ernest answered 16/2, 2018 at 1:29 Comment(0)
E
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()
Enormous answered 16/8, 2022 at 5:25 Comment(0)
E
0

it can be easier

instead:

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

write:

super().__init__(self, icon, parent)
Enormous answered 13/8, 2022 at 7:50 Comment(0)
I
0

I tested this, it Works in PyQt6 you should write the self in your code it's very important. if you not write self, maybe your program crashed.

you can show message with .showMessage
like: self.my_tray.showMessage("My Title", "Hello!")

you can set the context menu with .setContextMenu

and you can show tray icon with .show or .setVisible(True)

My Code:

from PyQt6.QtWidgets import *
from PyQt6.QtGui import *
from PyQt6.QtCore import * 
 
tray tray_context_menu=QMenu()
systemtray_context_menu.addAction("First Action")
tray_context_menu.addSeparator()                         
tray_context_menu.addAction("Second Action")


self.my_tray = QSystemTrayIcon(self)
self.my_tray.setIcon(QIcon("my_icon.png"))
self.my_tray.setToolTip("Tool Tip")
self.my_tray.setContextMenu(tray_context_menu)
self.my_tray.setVisible(True)  # show
self.my_tray.show()  # show


self.my_tray.showMessage("My Title", "Welcome to My          
Program")

self.my_tray.activated.connect(self.onTrayIconActivated)

def onTrayIconActivated(self, reason):
     if reason == QtGui.QSystemTrayIcon.Trigger:
         print(">>> system tray icon clicked")
         
     if reason == QtGui.QSystemTrayIcon.DoubleClick:
         print(">>> system tray icon double clicked")
Iredale answered 21/6, 2024 at 11:31 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.