[PySide6] QSystemTrayIcon bug when using Class
-
Hi,
I am trying to create a multiplatform system tray application.
My code is tested on macOS 14.2 with Python 3.11.4 and PySide 6.6.1 .
Here is my code :
import sys from PySide6 import QtCore, QtWidgets, QtGui class Tray(QtWidgets.QSystemTrayIcon): def __init__(self): super().__init__() self.setIcon(QtGui.QIcon("logo.icns")) self.setVisible(True) menu = QtWidgets.QMenu() action = QtGui.QAction("Hello World !") menu.addAction(action) quit = QtGui.QAction("Quit") quit.triggered.connect(app.quit) menu.addAction(quit) self.setContextMenu(menu) if __name__ == "__main__": app = QtWidgets.QApplication([]) app.setQuitOnLastWindowClosed(False) app.setWindowIcon(QtGui.QIcon("logo.icns")) tray = Tray() sys.exit(app.exec())
In this example, the icon in the system tray appears but nothing append when I click on it.
If I omit the class and put the code directly in my __name__ block, I works great.
import sys from PySide6 import QtCore, QtWidgets, QtGui if __name__ == "__main__": app = QtWidgets.QApplication([]) app.setQuitOnLastWindowClosed(False) app.setWindowIcon(QtGui.QIcon("logo.icns")) tray = QtWidgets.QSystemTrayIcon() tray.setIcon(QtGui.QIcon("logo.icns")) tray.setVisible(True) menu = QtWidgets.QMenu() action = QtGui.QAction("Hello World !") menu.addAction(action) quit = QtGui.QAction("Quit") quit.triggered.connect(app.quit) menu.addAction(quit) tray.setContextMenu(menu) sys.exit(app.exec())
How can I use QSystemTrayIcon with a class to keep a code clean and structured ?
-
Try keeping a reference to the menu in the class:
self._menu = QMenu()
...
-
Try keeping a reference to the menu in the class:
self._menu = QMenu()
...
@friedemannkleint
You are doubtless correct (your posts usually are!) for some Python reference counting reason this is correct. (It is certainly worth the OP trying.) But why doesn't the existing lineself.setContextMenu(menu)
keep the required reference count tomenu
above 0 here anyway? -
Try keeping a reference to the menu in the class:
self._menu = QMenu()
...
@friedemannkleint
Same problem withself.menu
. -
@friedemannkleint
Same problem withself.menu
.@Salamafet
Hmm, then as I suggested it's not that.In this example, the icon in the system tray appears but nothing append when I click on it.
You see the tray icon? When you click on it you do not get to see any
Quit
action on it, or you see the action but nothing happens when you click on it?Like you I am not clear what difference the class is making. If you are saying the item appears but the
quit
action is not triggered tryquit.triggered.connect(lambda: print("action triggered"))
If you want to get further any
QObject
-derived object (most of the ones you create) can haveobject.destroyed.connect(lambda: print("object destroyed"))
attached. Would tell you if anything unexpected is getting destroyed after you created it. -
@Salamafet
Hmm, then as I suggested it's not that.In this example, the icon in the system tray appears but nothing append when I click on it.
You see the tray icon? When you click on it you do not get to see any
Quit
action on it, or you see the action but nothing happens when you click on it?Like you I am not clear what difference the class is making. If you are saying the item appears but the
quit
action is not triggered tryquit.triggered.connect(lambda: print("action triggered"))
If you want to get further any
QObject
-derived object (most of the ones you create) can haveobject.destroyed.connect(lambda: print("object destroyed"))
attached. Would tell you if anything unexpected is getting destroyed after you created it.I see the tray icon, but there is no dropdown menu when I click on it.
This is like there is no action in the menu.The behavior is the same as if I used this code :
import sys from PySide6 import QtCore, QtWidgets, QtGui if __name__ == "__main__": app = QtWidgets.QApplication([]) app.setQuitOnLastWindowClosed(False) app.setWindowIcon(QtGui.QIcon("logo.icns")) tray = QtWidgets.QSystemTrayIcon() tray.setIcon(QtGui.QIcon("logo.icns")) tray.setVisible(True) sys.exit(app.exec())
-
I see the tray icon, but there is no dropdown menu when I click on it.
This is like there is no action in the menu.The behavior is the same as if I used this code :
import sys from PySide6 import QtCore, QtWidgets, QtGui if __name__ == "__main__": app = QtWidgets.QApplication([]) app.setQuitOnLastWindowClosed(False) app.setWindowIcon(QtGui.QIcon("logo.icns")) tray = QtWidgets.QSystemTrayIcon() tray.setIcon(QtGui.QIcon("logo.icns")) tray.setVisible(True) sys.exit(app.exec())
@Salamafet
Yet you say if you move theaction
/addAction()
code out of the class and into the__main__
area you do see it?Although I absolutely do not see why this should be necessary, try
self._menu = QtWidgets.QMenu() self._action = QtGui.QAction("Hello World !") # this line too self._menu.addAction(self._action)
when inside the class?
-
@Salamafet
Yet you say if you move theaction
/addAction()
code out of the class and into the__main__
area you do see it?Although I absolutely do not see why this should be necessary, try
self._menu = QtWidgets.QMenu() self._action = QtGui.QAction("Hello World !") # this line too self._menu.addAction(self._action)
when inside the class?
-
-
Yet you say if you move the action/addAction() code out of the class and into the main area you do see it?
Yes, in this case it's works.
By adding self on each variable in class, it's works. I don't understand why, but it works.
Thanks a lot
@Salamafet said in [PySide6] QSystemTrayIcon bug when using Class:
I don't understand why, but it works.
Well the reason is that when Python detects nothing is refencing any Python object it destroys it --- that's the object collection the language gices you unlike plain C++.
The mystery is that one would have thought that
self.setContextMenu(menu)
would itself increment the reference count onmenu
and its action added to that. I don't know why it does not. I don't know whethermenu = QtWidgets.QMenu(self)
insideTray()
would have improved the situation. You might like to add that anyway, and then maybe see if the other 2 changes are no longer required.Ah, see (the C++ explanation for) [void]QSystemTrayIcon::setContextMenu(QMenu *menu) (or PySide6 PySide6.QtWidgets.QSystemTrayIcon.setContextMenu(menu)):
Note: The system tray icon does not take ownership of the menu. You must ensure that it is deleted at the appropriate time by, for example, creating the menu with a suitable parent object.
This explains everything. Whenever it says "something does not take ownership of this
QObject
" that will mean the Python code will not increment a reference count.setContextMenu()
leaves you in charge of the menu, which e.g. you might be using elsewhere. Consequently anyQObject
s documented like that will require (typically) a Pythonself._variable = TheObject()
to keep it "alive" for the duration of the class instance, not just a local variable. Or, by the looks of it even better, passself
to the top-level object's constructor (yourQMenu()
) so it is fully owned by the instance.