<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - qsystemtrayicon</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/qsystemtrayicon.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2021-01-15T09:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>QSystemTrayIcon — Adding Menu Items in a Loop — Why dynamically created QActions disappear and how to fix it</title><link href="https://www.pythonguis.com/faq/qsystemtrayicon-example/" rel="alternate"/><published>2021-01-15T09:00:00+00:00</published><updated>2021-01-15T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2021-01-15:/faq/qsystemtrayicon-example/</id><summary type="html">I'm trying to populate a &lt;code&gt;QSystemTrayIcon&lt;/code&gt; context menu using a for loop, but only the last item shows up. What happened to the other entries, and how do I fix it?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I'm trying to populate a &lt;code&gt;QSystemTrayIcon&lt;/code&gt; context menu using a for loop, but only the last item shows up. What happened to the other entries, and how do I fix it?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you've ever tried to build a system tray menu dynamically &amp;mdash; say, by looping through a list of items &amp;mdash; you may have run into a confusing problem: only the last menu item appears. The earlier ones vanish without any error message. Let's look at why this happens and how to solve it.&lt;/p&gt;
&lt;h2 id="the-problem-disappearing-menu-items"&gt;The problem: disappearing menu items&lt;/h2&gt;
&lt;p&gt;Here's a minimal example that demonstrates the issue. We have a list of three entries and we loop through them, creating a &lt;code&gt;QAction&lt;/code&gt; for each one:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6.QtWidgets import QApplication, QSystemTrayIcon, QMenu
from PyQt6.QtGui import QAction, QIcon


app = QApplication([])
app.setQuitOnLastWindowClosed(False)

icon = QIcon.fromTheme("application")
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)

menu = QMenu()
entries = ["One", "Two", "Three"]
for entry in entries:
    action = QAction(entry)
    menu.addAction(action)
    action.triggered.connect(app.quit)

tray.setContextMenu(menu)

app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You'd expect to see three items in the menu &amp;mdash; "One", "Two", and "Three" &amp;mdash; but only "Three" appears. The first two have vanished.&lt;/p&gt;
&lt;h2 id="why-this-happens-python-garbage-collection"&gt;Why this happens: Python garbage collection&lt;/h2&gt;
&lt;p&gt;The cause is Python's garbage collector. When you create an object and assign it to a variable, Python keeps that object alive as long as something is referencing it. Once nothing references it anymore, Python is free to delete it.&lt;/p&gt;
&lt;p&gt;In the loop above, the variable &lt;code&gt;action&lt;/code&gt; is reassigned on every iteration. On the second pass through the loop, &lt;code&gt;action&lt;/code&gt; now points to the new &lt;code&gt;QAction("Two")&lt;/code&gt;, and the previous &lt;code&gt;QAction("One")&lt;/code&gt; no longer has any Python reference. Python garbage-collects it, which also destroys the underlying Qt C++ object &amp;mdash; and removes it from the menu.&lt;/p&gt;
&lt;p&gt;By the time the loop finishes, only the last &lt;code&gt;QAction&lt;/code&gt; survives because &lt;code&gt;action&lt;/code&gt; still points to it.&lt;/p&gt;
&lt;p&gt;This is a common source of confusion when working with PyQt6 (and PySide6). Qt's C++ side doesn't always take full ownership of objects you pass to it, so Python's garbage collector can sweep them away unexpectedly.&lt;/p&gt;
&lt;h2 id="the-fix-keep-references-to-your-actions"&gt;The fix: keep references to your actions&lt;/h2&gt;
&lt;p&gt;The solution is straightforward &amp;mdash; store each &lt;code&gt;QAction&lt;/code&gt; somewhere so Python keeps it alive. A simple list works perfectly:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6.QtWidgets import QApplication, QSystemTrayIcon, QMenu
from PyQt6.QtGui import QAction, QIcon


app = QApplication([])
app.setQuitOnLastWindowClosed(False)

icon = QIcon.fromTheme("application")
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)

menu = QMenu()
entries = ["One", "Two", "Three"]
actions = []
for entry in entries:
    action = QAction(entry)
    menu.addAction(action)
    action.triggered.connect(app.quit)
    actions.append(action)

tray.setContextMenu(menu)

app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The only change is the &lt;code&gt;actions&lt;/code&gt; list. By appending each &lt;code&gt;QAction&lt;/code&gt; to this list, we ensure that every action has a live Python reference for the lifetime of the program. Now all three items appear in the menu as expected.&lt;/p&gt;
&lt;h2 id="an-alternative-set-the-menu-as-the-actions-parent"&gt;An alternative: set the menu as the action's parent&lt;/h2&gt;
&lt;p&gt;Another approach is to pass a parent to each &lt;code&gt;QAction&lt;/code&gt;. When a Qt object has a parent, Qt takes ownership of it and manages its lifetime. This means Python's garbage collector won't destroy it, because the C++ side is keeping it alive:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6.QtWidgets import QApplication, QSystemTrayIcon, QMenu
from PyQt6.QtGui import QAction, QIcon


app = QApplication([])
app.setQuitOnLastWindowClosed(False)

icon = QIcon.fromTheme("application")
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)

menu = QMenu()
entries = ["One", "Two", "Three"]
for entry in entries:
    action = QAction(entry, menu)  # menu is the parent
    menu.addAction(action)
    action.triggered.connect(app.quit)

tray.setContextMenu(menu)

app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;By passing &lt;code&gt;menu&lt;/code&gt; as the second argument to &lt;code&gt;QAction&lt;/code&gt;, each action becomes a child of the menu. Qt will keep the action alive as long as the menu exists, so there's no need for a separate list.&lt;/p&gt;
&lt;p&gt;Both approaches work well. Using a parent is often the cleaner option when you don't need to reference the actions later. Keeping a list is useful when you want to modify or remove specific actions after creation.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete working example&lt;/h2&gt;
&lt;p&gt;Here's a slightly more fleshed-out version that gives each menu item its own behavior, so you can see how to connect different actions to different slots:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PyQt6.QtWidgets import QApplication, QSystemTrayIcon, QMenu
from PyQt6.QtGui import QAction, QIcon


def on_action_triggered(text):
    print(f"Selected: {text}")


app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)

icon = QIcon.fromTheme("application")
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)

menu = QMenu()

entries = ["One", "Two", "Three"]
actions = []
for entry in entries:
    action = QAction(entry, menu)
    menu.addAction(action)
    # Use a default argument to capture the current value of entry
    action.triggered.connect(lambda checked, text=entry: on_action_triggered(text))
    actions.append(action)

# Add a separator and a quit option
menu.addSeparator()
quit_action = QAction("Quit", menu)
quit_action.triggered.connect(app.quit)
menu.addAction(quit_action)

tray.setContextMenu(menu)

sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Notice the &lt;code&gt;lambda&lt;/code&gt; with a default argument (&lt;code&gt;text=entry&lt;/code&gt;). This is a standard Python pattern for capturing the current loop variable's value inside a closure. Without &lt;code&gt;text=entry&lt;/code&gt;, every lambda would reference the same &lt;code&gt;entry&lt;/code&gt; variable and they'd all print "Three".&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;When you create Qt objects in a loop and they seem to disappear, the cause is almost always garbage collection. Python cleans up objects that no longer have references, and if you keep reassigning the same variable in a loop, only the last object survives.&lt;/p&gt;
&lt;p&gt;The fix is to either store your objects in a list or give them a Qt parent so the C++ side manages their lifetime. This applies not just to &lt;code&gt;QAction&lt;/code&gt; and menus, but to any Qt object you create dynamically &amp;mdash; widgets, layouts, timers, and more. Once you're aware of this pattern, you'll recognize it immediately whenever things start vanishing.&lt;/p&gt;
&lt;p&gt;For a complete guide to building system tray applications with menus, icons, and more, see the &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-system-tray-mac-menu-bar-applications/"&gt;System Tray &amp;amp; Mac Menu Bar Applications&lt;/a&gt; tutorial. If you'd like to learn more about how signals, slots, and the &lt;code&gt;lambda&lt;/code&gt; pattern used above work in practice, take a look at &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;Signals, Slots &amp;amp; Events&lt;/a&gt;. To understand how actions, toolbars, and menus fit together in larger applications, the &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-actions-toolbars-menus/"&gt;Actions, Toolbars &amp;amp; Menus&lt;/a&gt; tutorial covers these topics in depth. If you're new to PyQt6 and want to get your environment set up, see the &lt;a href="https://www.pythonguis.com/installation/install-pyqt6-windows/"&gt;PyQt6 installation guide for Windows&lt;/a&gt;.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PySide6 see my book, &lt;a href="https://www.martinfitzpatrick.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="python"/><category term="qsystemtrayicon"/><category term="qaction"/><category term="qmenu"/><category term="qt"/><category term="qt6"/></entry><entry><title>System Tray Icons Not Showing or Not Closing on Windows — Troubleshooting common issues with QSystemTrayIcon on Windows 10 and 11</title><link href="https://www.pythonguis.com/faq/system-tray-examples-not-showing-up-on-windows-10/" rel="alternate"/><published>2020-05-14T09:00:00+00:00</published><updated>2020-05-14T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2020-05-14:/faq/system-tray-examples-not-showing-up-on-windows-10/</id><summary type="html">I'm following a system tray tutorial on Windows 10. When I run the example code, nothing happens &amp;mdash; no icon shows up in the system tray, and I have to force the program to stop. Even when the icon does appear, quitting from the menu doesn't fully close the app &amp;mdash; the icon lingers and I have to use Task Manager. Other examples from the web have the same problem. What's going on?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I'm following a system tray tutorial on Windows 10. When I run the example code, nothing happens &amp;mdash; no icon shows up in the system tray, and I have to force the program to stop. Even when the icon does appear, quitting from the menu doesn't fully close the app &amp;mdash; the icon lingers and I have to use Task Manager. Other examples from the web have the same problem. What's going on?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a surprisingly common issue, and there are a few different things that can cause it. The good news is that most of them are straightforward to fix once you know what to look for. Let's walk through the main causes and solutions.&lt;/p&gt;
&lt;h2 id="the-icon-file-is-missing-or-in-the-wrong-place"&gt;The icon file is missing or in the wrong place&lt;/h2&gt;
&lt;p&gt;The most common reason a system tray icon doesn't appear at all on Windows is that the icon file can't be found. Unlike on some other platforms, Windows will silently show &lt;em&gt;nothing&lt;/em&gt; if the icon path is wrong &amp;mdash; no error, no placeholder, just an empty system tray.&lt;/p&gt;
&lt;p&gt;If your code looks something like this:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;tray = QSystemTrayIcon(QIcon("icon.png"))
tray.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then &lt;code&gt;icon.png&lt;/code&gt; must be in the same directory you're running the script from. That's the &lt;em&gt;current working directory&lt;/em&gt;, which might not be the same as the folder your script lives in &amp;mdash; especially if you're running from an IDE.&lt;/p&gt;
&lt;p&gt;You can make the path more reliable by building it relative to the script file:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import os

basedir = os.path.dirname(__file__)
icon_path = os.path.join(basedir, "icon.png")
tray = QSystemTrayIcon(QIcon(icon_path))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Or, to avoid the icon-file problem entirely while you're testing, you can use one of Qt's built-in standard icons:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;style = app.style()
icon = QIcon(style.standardIcon(QStyle.StandardPixmap.SP_ComputerIcon))
tray = QSystemTrayIcon(icon)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This guarantees you'll see &lt;em&gt;something&lt;/em&gt; in the tray, which helps you confirm the rest of your code is working.&lt;/p&gt;
&lt;h2 id="windows-hides-new-tray-icons"&gt;Windows hides new tray icons&lt;/h2&gt;
&lt;p&gt;Windows 10 and 11 sometimes hide system tray icons automatically. Your app might actually be running, but the icon is tucked away in the overflow area. Click the small upward arrow (&lt;code&gt;^&lt;/code&gt;) on the taskbar to expand the hidden icons and check if your icon is there.&lt;/p&gt;
&lt;p&gt;You can change this behavior in Windows Settings under &lt;strong&gt;Taskbar &amp;gt; Other system tray icons&lt;/strong&gt; (Windows 11) or &lt;strong&gt;Taskbar settings &amp;gt; Select which icons appear on the taskbar&lt;/strong&gt; (Windows 10).&lt;/p&gt;
&lt;h2 id="ides-can-interfere-with-the-event-loop"&gt;IDEs can interfere with the event loop&lt;/h2&gt;
&lt;p&gt;This is the issue that causes the most confusion. If you're running your system tray app from inside an IDE like Spyder, Jupyter Notebook, or even PyCharm in certain configurations, the app may not shut down cleanly when you call &lt;code&gt;app.quit()&lt;/code&gt;. The icon can linger in the tray, the process can hang, and you end up reaching for Task Manager.&lt;/p&gt;
&lt;p&gt;Why does this happen? IDEs often run your Python code inside their own process or kernel. When your Qt application calls &lt;code&gt;app.quit()&lt;/code&gt;, it stops the Qt event loop &amp;mdash; but the IDE's surrounding process keeps running. The operating system sees the process is still alive, so it keeps the tray icon around.&lt;/p&gt;
&lt;p&gt;The simplest way to confirm whether this is an IDE issue is to &lt;strong&gt;run your script from the command line&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;python systray.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If the app starts, shows the icon, and quits cleanly from the command line, then the problem is with your IDE, not your code.&lt;/p&gt;
&lt;h3&gt;Working around IDE issues&lt;/h3&gt;
&lt;p&gt;Here are a few approaches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Run from the terminal.&lt;/strong&gt; This is the most reliable option for testing system tray apps. Save your script to a &lt;code&gt;.py&lt;/code&gt; file and run it with &lt;code&gt;python yourscript.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use PyCharm with a clean virtual environment.&lt;/strong&gt; PyCharm's run configurations tend to behave more predictably than Spyder or Jupyter for GUI applications. Create a virtual environment, install PyQt6 (or PySide6), and run from there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoid Jupyter Notebook for GUI apps.&lt;/strong&gt; Jupyter runs code in a kernel that doesn't play well with Qt's event loop. It's a great tool for data exploration, but GUI application development is better done elsewhere.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="make-sure-you-have-a-proper-quit-action"&gt;Make sure you have a proper quit action&lt;/h2&gt;
&lt;p&gt;A system tray application has no main window by default, so there's no "X" button to close it. You need to provide a way for the user to quit &amp;mdash; typically through the right-click context menu. Without this, the only way to stop the app is to kill the process.&lt;/p&gt;
&lt;p&gt;Here's how to add a quit action to the context menu:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;quit_action = QAction("Quit")
quit_action.triggered.connect(app.quit)
menu.addAction(quit_action)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Also, because there's no main window, you need to tell Qt not to quit when the last window is closed (since there was never a window to begin with):&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;app.setQuitOnLastWindowClosed(False)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Without this line, the application might exit immediately on some platforms, or behave unpredictably.&lt;/p&gt;
&lt;h2 id="remember-to-right-click"&gt;Remember to right-click&lt;/h2&gt;
&lt;p&gt;This catches people out more often than you'd expect. On Windows, system tray icons respond to &lt;strong&gt;right-click&lt;/strong&gt;, not left-click. Right-clicking opens the context menu where your actions (like "Quit") live. Left-clicking a tray icon can trigger an "activated" signal, but only if you've connected something to it.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete working example&lt;/h2&gt;
&lt;p&gt;Here's a full, self-contained system tray application for PyQt6 that uses a built-in icon so you don't need any image files. You can copy this, save it as &lt;code&gt;systray.py&lt;/code&gt;, and run it from the command line:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PyQt6.QtGui import QAction, QIcon
from PyQt6.QtWidgets import (
    QApplication,
    QMenu,
    QStyle,
    QSystemTrayIcon,
)

app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)

# Use a built-in icon so we don't need an image file.
style = app.style()
icon = QIcon(style.standardIcon(QStyle.StandardPixmap.SP_ComputerIcon))

# Create the system tray icon.
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setToolTip("My Tray App")

# Build the right-click context menu.
menu = QMenu()

action_hello = QAction("Say Hello")
action_hello.triggered.connect(lambda: print("Hello from the system tray!"))
menu.addAction(action_hello)

menu.addSeparator()

action_quit = QAction("Quit")
action_quit.triggered.connect(app.quit)
menu.addAction(action_quit)

tray.setContextMenu(menu)

# Show the tray icon.
tray.setVisible(True)

sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run it with:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;$ python systray.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You should see an icon appear in your system tray. Right-click it to see the menu. Choose "Say Hello" to print a message to the terminal, or "Quit" to close the app cleanly.&lt;/p&gt;
&lt;p&gt;And here's the same example for PySide6:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import (
    QApplication,
    QMenu,
    QStyle,
    QSystemTrayIcon,
)

app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)

# Use a built-in icon so we don't need an image file.
style = app.style()
icon = QIcon(style.standardIcon(QStyle.StandardPixmap.SP_ComputerIcon))

# Create the system tray icon.
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setToolTip("My Tray App")

# Build the right-click context menu.
menu = QMenu()

action_hello = QAction("Say Hello")
action_hello.triggered.connect(lambda: print("Hello from the system tray!"))
menu.addAction(action_hello)

menu.addSeparator()

action_quit = QAction("Quit")
action_quit.triggered.connect(app.quit)
menu.addAction(action_quit)

tray.setContextMenu(menu)

# Show the tray icon.
tray.setVisible(True)

sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.martinfitzpatrick.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="pyside6"/><category term="pyside"/><category term="python"/><category term="windows"/><category term="qsystemtrayicon"/><category term="system-tray"/><category term="qt"/><category term="qt6"/></entry></feed>