34. System tray & macOS menus
System tray applications (or menu bar applications) can be useful for making common functions available in a small number of clicks. For full desktop applications they’re a useful shortcut to control apps without opening up the whole window.
Qt provides a simple interface for building cross-platform system tray (Windows) or menu bar (macOS) apps. Below is a minimal working example for showing an icon in the toolbar/system tray with a menu. The action in the menu isn’t connected and so doesn’t do anything yet.
Listing 242. further/systray.py
import os
import sys
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import (
QAction,
QApplication,
QColorDialog,
QMenu,
QSystemTrayIcon,
)
basedir = os.path.dirname(__file__)
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
# Create the icon
icon = QIcon(os.path.join(basedir, "icon.png"))
# Create the tray
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)
# Create the menu
menu = QMenu()
action = QAction("A menu item")
menu.addAction(action)
# Add a Quit option to the menu.
quit = QAction("Quit")
quit.triggered.connect(app.quit)
menu.addAction(quit)
# Add the menu to the tray
tray.setContextMenu(menu)
app. exec ()
You’ll notice that there isn’t a QMainWindow, simply because we don’t have any window to show. You can create a window as normal without affecting the behavior of the system tray icon.
ë
The default behavior in Qt is to close an application once all the
active windows have closed. This won’t affect this toy example,
but will be an issue in application where you do create windows
and then close them. Setting
app.setQuitOnLastWindowClosed(False) stops this and will ensure
your application keeps running.
The provided icon shows up in the toolbar (you can see it on the left hand side of the icons grouped on the right of the system tray or menubar).
Figure 236. The icon showing on the menubar.
Clicking (or right-clicking on Windows) on the icon shows the added menu.
Figure 237. The menubar app menu.
This application doesn’t do anything yet, so in the next part we’ll expand this example to create a mini color-picker.
Below is a more complete working example using the built in QColorDialog from Qt to give a toolbar accessible color picker. The menu lets you choose to get the picked color as HTML-format #RRGGBB, rgb(R,G,B) or hsv(H,S,V).
Listing 243. further/systray_color.py
import os
import sys
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import (
QAction,
QApplication,
QColorDialog,
QMenu,
QSystemTrayIcon,
)
basedir = os.path.dirname(__file__)
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
# Create the icon
icon = QIcon(os.path.join(basedir, "color.png"))
clipboard = QApplication.clipboard()
dialog = QColorDialog()
def copy_color_hex ():
if dialog. exec ():
color = dialog.currentColor()
clipboard.setText(color.name())
def copy_color_rgb ():
if dialog. exec ():
color = dialog.currentColor()
clipboard.setText(
"rgb(%d, %d, %d)"
% (color.red(), color.green(), color.blue())
)
def copy_color_hsv ():
if dialog. exec ():
color = dialog.currentColor()
clipboard.setText(
"hsv(%d, %d, %d)"
% (color.hue(), color.saturation(), color.value())
)
# Create the tray
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)
# Create the menu
menu = QMenu()
action1 = QAction("Hex")
action1.triggered.connect(copy_color_hex)
menu.addAction(action1)
action2 = QAction("RGB")
action2.triggered.connect(copy_color_rgb)
menu.addAction(action2)
action3 = QAction("HSV")
action3.triggered.connect(copy_color_hsv)
menu.addAction(action3)
quit = QAction("Quit")
quit.triggered.connect(app.quit)
menu.addAction(quit)
# Add the menu to the tray
tray.setContextMenu(menu)
app. exec ()
As in the previous example there is no QMainWindow for this example. The menu is
created as before, but adding 3 actions for the different output formats. Each action is connected to a specific handler function for the format it represents. Each handler shows a dialog and, if a color is selected, copies that color to the clipboard in the given format.
As before, the icon appears in the toolbar.
Figure 238. The Color picker on the toolbar.
Clicking the icon shows a menu, from which you can select the format of image you want to return.
Figure 239. The Color picker menu
Once you’ve chosen the format, you’ll see the standard Qt color picker window.
Figure 240. The system Color picker window
Select the color you want and click OK. The chosen color will be copied to the clipboard in the requested format. The formats available will product the following output:
Value Ranges
#a2b3cc 00-FF
rgb(25, 28, 29) 0-255
hsv(14, 93, 199) 0-255
Adding a system tray icon for a full app
So far we’ve shown how to create a standalone system tray application with no main window. However, sometimes you may wish to have a system tray icon as well as a window. When this is done, typically the main window can be opened
and closed (hidden) from the tray icon, without closing the application. In this section we’ll look at how to build this kind of application with Qt5.
In principle it’s quite straightforward — create our main window, and connect a signal from an action to the .show() method of the window.
Below is a small tray notes application called "PenguinNotes". When run, it puts a small penguin icon in your system track or macOS toolbar.
Clicking the small penguin icon in the tray will show the window. The window contains a QTextEdit editor into which you can write notes. You can close the window as normal, or by clicking again on the tray icon. The and app will remain
running in the tray. To close the app you can use File › Close — closing will automatically save the notes.
Listing 244. further/systray_window.py
import os
import sys
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import (
QAction,
QApplication,
QMainWindow,
QMenu,
QSystemTrayIcon,
QTextEdit,
)
basedir = os.path.dirname(__file__)
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
# Create the icon
icon = QIcon(os.path.join(basedir, "animal-penguin.png"))
Create the tray
tray = QSystemTrayIcon() tray.setIcon(icon) tray.setVisible(True)
class MainWindow (QMainWindow): def init (self): super().init()
self.editor = QTextEdit() self.load() # Load up the text from file.
menu = self.menuBar() file_menu = menu.addMenu("&File")
self.reset = QAction("&Reset") self.reset.triggered.connect(self.editor.clear) file_menu.addAction(self.reset)
self.quit = QAction("&Quit") self.quit.triggered.connect(app.quit) file_menu.addAction(self.quit)
self.setCentralWidget(self.editor)
self.setWindowTitle("PenguinNotes")
def load (self): with open("notes.txt", "r") as f: text = f.read() self.editor.setPlainText(text)
def save (self): text = self.editor.toPlainText() with open("notes.txt", "w") as f: f.write(text)
def activate (self, reason): if ( reason == QSystemTrayIcon.ActivationReason.Trigger ): # Icon clicked. self.show()
w = MainWindow()
tray.activated.connect(w.activate)
app.aboutToQuit.connect(w.save)
app. exec ()
Z
On macOS the Quit action will appear in the application menu
(on the far left, with the application name), not the File menu. If
we didn’t also add the File › Reset action, the File menu would
be empty and hidden (try it!)
Below is a screenshot of the notes app with the window open.
Figure 241. The notes editor window.
The control of showing and hiding the window is handled in the activate method on our QMainWindow. This is connected to the tray icon .activated signal at the bottom of the code, using tray.activated.connect(w.activate).
def activate (self, reason):
if reason == QSystemTrayIcon.Trigger: # Icon clicked.
if self.isVisible():
self.hide()
else :
self.show()
This signal is triggered under a number of different circumstances, so we must first check to ensure we are only using QSystemTrayIcon.Trigger.
Reason Value Description
QSystemTrayIcon.Unknown 0 Unknown reason.
QSystemTrayIcon.Context 1 Context menu requested (single click
macOS, right-click Windows).
QSystemTrayIcon.DoubleClick 2 Icon double clicked. On macOS double-
click only fires if no context menu is
set, as the menu opens with a single
click.
QSystemTrayIcon.Trigger 3 Icon clicked once.
QSystemTrayIcon.MiddleClick 4 Icon clicked with the middle mouse
button.
By listening to these events you should be able to construct any type of system tray behavior you wish. However, be sure to check the behavior on all your target platforms.
'PyQt5_' 카테고리의 다른 글
Working with command-line arguments (0) | 2023.03.13 |
---|---|
Enums & the Qt Namespace (0) | 2023.03.13 |
Working with Relative Paths (0) | 2023.03.13 |
Extending Signals (0) | 2023.03.13 |
Extending Signals (0) | 2023.03.13 |
댓글