You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5+infi.systray程序点击托盘Open按钮后无响应问题求助

问题原因与修复方案

核心原因

你遇到的窗口无响应问题,本质是两个关键错误导致的:

  • 跨线程UI操作违规:infi.systray的菜单回调函数是在非Qt主线程中执行的,但PyQt要求所有UI操作(比如show())必须在主线程进行,直接在回调里调用MainWindow.show()会打乱UI线程的事件循环,导致窗口失去响应。
  • 默认关闭行为触发程序退出:点击窗口关闭按钮时,Qt默认会终止整个程序的事件循环,虽然托盘图标看起来还在,但后续的UI操作已经没有正常的线程环境支撑了。

修复步骤

我们针对这两个问题逐一解决:

  1. 重写窗口关闭事件,改为隐藏窗口
    自定义QMainWindow子类,重写closeEvent方法,让点击关闭按钮时只隐藏窗口,而不是退出程序,保证Qt事件循环持续运行。

  2. 强制UI操作在主线程执行
    利用Qt的QMetaObject.invokeMethod机制,将托盘回调里的UI操作切换到主线程执行,避免跨线程操作的冲突。

  3. 优化托盘对象生命周期
    将systray作为类成员变量,避免局部变量被垃圾回收导致托盘图标意外消失。

修改后的完整代码

import sys
from infi.systray import SysTrayIcon
from PyQt5 import QtCore, QtGui, QtWidgets

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        # 初始化托盘图标
        self.init_systray()

    def closeEvent(self, event):
        # 点击关闭按钮时隐藏窗口,忽略默认的退出行为
        self.hide()
        event.ignore()

    def init_systray(self):
        def open_app(systray):
            # 通过invokeMethod确保show()在主线程执行
            QtCore.QMetaObject.invokeMethod(
                self,
                "show",
                QtCore.Qt.QueuedConnection
            )
        def info(systray):
            print("Info")
        def help_app(systray):
            print("Help")

        menu_options = (("Open", None, open_app), ("Info", None, info), ("Help", None, help_app))
        self.systray = SysTrayIcon("/icons/logo.ico", "App", menu_options)
        self.systray.start()

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(555,333)
        MainWindow.setMinimumSize(QtCore.QSize(555, 333))
        MainWindow.setMaximumSize(QtCore.QSize(555, 333))
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setMinimumSize(QtCore.QSize(555, 333))
        self.centralwidget.setMaximumSize(QtCore.QSize(555, 333))
        font = QtGui.QFont()
        font.setFamily("Comfortaa")
        font.setPointSize(15)
        self.centralwidget.setFont(font)
        self.centralwidget.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
        self.centralwidget.setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(150, 2, 255, 255), stop:1 rgba(255, 0, 230, 255));")
        self.centralwidget.setObjectName("centralwidget")
        # Logo
        self.logo = QtWidgets.QLabel(self.centralwidget)
        self.logo.setGeometry(QtCore.QRect(50, 30, 201, 221))
        self.logo.setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 rgba(0, 0, 0, 0), stop:1 rgba(255, 255, 255, 0));")
        self.logo.setText("")
        self.logo.setPixmap(QtGui.QPixmap("/photos/logo.png"))
        self.logo.setObjectName("logo")
        # Rules
        self.Rules = QtWidgets.QLabel(self.centralwidget)
        self.Rules.setGeometry(QtCore.QRect(300, 30, 300, 200))
        self.Rules.setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 rgba(0, 0, 0, 0), stop:1 rgba(255, 255, 255, 0));\nfont: 25 10pt \"Comfortaa\";")
        self.Rules.setWordWrap(True)
        self.Rules.setObjectName("Rules")
        MainWindow.setCentralWidget(self.centralwidget)
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Program"))
        self.Rules.setText(_translate("MainWindow", "Short rules how to use it:"))

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    app_icon = QtGui.QIcon()
    app_icon.addFile('/icons/logo.ico')
    app.setWindowIcon(app_icon)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

关键修改说明

  • 分离窗口与UI类:自定义MainWindow类,方便重写关闭事件和管理托盘对象,避免原代码中UI类与窗口逻辑混杂的问题。
  • closeEvent重写:self.hide()隐藏窗口,event.ignore()阻止程序退出,确保Qt事件循环始终正常运行。
  • 主线程调用UI操作:QMetaObject.invokeMethod通过QueuedConnection将show()操作加入主线程的事件队列,严格遵循PyQt的线程规则。
  • 托盘对象持久化:将systray设为类成员,避免函数执行完毕后被垃圾回收,保证托盘图标一直可用。

这样修改后,点击关闭按钮窗口会隐藏到托盘,点击托盘的Open按钮就能正常显示并响应所有操作了。

内容的提问来源于stack exchange,提问作者user14023416

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:49:43