PyQt:在系统托盘应用程序中显示菜单

29

首先,我是一名有经验的C程序员,但是对Python还很新。我想使用pyqt创建一个简单的应用程序。让我们假设这个应用程序非常简单,运行时必须将一个图标放在系统托盘中,并在其菜单中提供退出应用程序的选项。

这段代码可以工作,它显示了菜单(为了保持简单,我没有连接退出操作等)。

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

但是这个不行:

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

我可能漏掉了一些东西。没有出现错误,但在第二种情况下,当我用鼠标右键单击时,它不显示菜单。


2
作为一名刚开始学习Python的C程序员,我也可以说“嗯嗯”。 - Aiden Bell
如果您的答案解决了问题,请选择社区维基版的答案作为“答案”(我不会获得任何声望:)。我还编辑了您的答案以修复一个小错误。 - tzot
8个回答

32

经过一些调试,我找到了问题所在。QMenu对象在finish __init__函数后被销毁,因为它没有父类。虽然QSystemTrayIcon的父类可以是一个QMenu对象,但它必须是一个QWidget对象。这段代码可以运行(请注意,QMenu获取与QSystemTrayIcon相同的父类,即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()

8
这里是实现了“退出”操作的代码。
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()

请注意:*您必须将qtLogo.png图像放置在与脚本相同的目录中。 - demosthenes

8

我认为以下方案更好,因为它似乎不依赖于QT的内部垃圾回收决策。

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

哇,所以这根本不是系统托盘? - fatuhoku

8

以下是PyQt5版本(能够实现demosthenes答案中的退出操作)。 从PyQt4移植到PyQt5的源代码

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
# code source: https://dev59.com/xHNA5IYBdhLWcg3wmfO5  - add answer PyQt5
#PyQt4 to PyQt5 version: https://dev59.com/8mIi5IYBdhLWcg3w_QiG

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)

你忘记在退出时添加事件:menu.triggered.connect(self.exit) - danilo

5

我尝试了以上提供的所有答案,但无法在PyQt5中使其工作(系统托盘菜单中的退出选项实际上不能退出),但我设法将它们组合起来得到了一个可行的解决方案。我仍在努力确定是否应该进一步使用exitAction。

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)

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

3

使用 pyqt5 连接事件:

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

2

对于PySide6: 在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()

在 silnik 文件夹中创建 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()

0

这可以更容易些。

相反:

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

写:

super().__init__(self, icon, parent)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接