使用 pyqt5 时,上下文菜单在托盘图标上不起作用

Context menu didn't work on tray icon when using pyqt5

我正在用 pyqt5 开发一个应用程序,它会在开始工作时自动隐藏 window。我希望构建一个带有上下文菜单的托盘图标以再次显示 window。下面是我的简短代码,

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.set_sytem_tray_icon()

        self.current_status = Stop
        self.track_str = ''
        self.start_button.clicked.connect(self.start_button_clicked)
        self.stop_button.clicked.connect(self.stop_button_clicked)

        self.show()

    def set_sytem_tray_icon(self):
        contextMenu = QMenu()
        action = contextMenu.addAction("Show Window")
        action.triggered.connect(self.show)
        contextMenu.addAction(action)
        self.tray_icon = QSystemTrayIcon()
        self.tray_icon.setIcon(QtGui.QIcon("img.ico"))
        self.tray_icon.setContextMenu(contextMenu)
        self.tray_icon.show()

    def get_specific_window(self, class_name, title):


    def check_ULink_status(self):
        try:
            while True:
                # Doing something
                    break

    def start_button_clicked(self):

        self.hide()

        thread = threading.Thread(target=self.check_ULink_status)
        thread.setDaemon(True)
        thread.start()
        thread.join()

        self.show()

    def stop_button_clicked(self):
        reply = QMessageBox.information(self, "Warning", "Stop", QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.current_status = Stop
            self.change_button_availability()

这是我的问题,当我单击开始按钮时,应用程序开始运行,但托盘图标没有响应任何操作。我相信我的主线程存在一些冲突,但我仍然无法弄清楚发生了什么。有人对此有答案吗?

更新, 我想提供另一个使用 qthread 的解决方案。

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.set_sytem_tray_icon()

        self.current_status = Stop
        self.track_str = ''
        self.start_button.clicked.connect(self.start_button_clicked)
        self.stop_button.clicked.connect(self.stop_button_clicked)

        self.show()

    def set_sytem_tray_icon(self):
        contextMenu = QMenu()
        action = contextMenu.addAction("Show Window")
        action.triggered.connect(self.show)
        contextMenu.addAction(action)
        self.tray_icon = QSystemTrayIcon()
        self.tray_icon.setContextMenu(contextMenu)
        self.tray_icon.show()

    def set_sytem_tray_icon_with_qthread(self):
        set_sytem_tray_icon_qthread = qthread_definition.MyQThread2()
        set_sytem_tray_icon_qthread.signal.connect(self.set_sytem_tray_icon)
        set_sytem_tray_icon_qthread.start()
        set_sytem_tray_icon_qthread.wait()

    def show_mainwindow_with_qthread(self):
        show_mainwindow_qthread = qthread_definition.MyQThread2()
        show_mainwindow_qthread.signal.connect(self.show)
        show_mainwindow_qthread.start()
        show_mainwindow_qthread.wait()

    def get_specific_window(self, class_name, title):
        # doing something

    def check_ULink_status(self):
        self.set_sytem_tray_icon_with_qthread()  # add new qthread here
        try:
            while True:
                # Doing something
                    break
            self.show_mainwindow_with_qthread()

    def start_button_clicked(self):
        self.hide()
        thread = threading.Thread(target=self.check_ULink_status)
        thread.setDaemon(True)
        thread.start()

    def stop_button_clicked(self):
        reply = QMessageBox.information(self, "Warning", "Stop", QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.current_status = Stop
            self.change_button_availability()

其中MyQThread2 class如下所示,

class MyQThread2(QtCore.QThread):
    # this thread is to create the tray icon
    signal = QtCore.pyqtSignal()

    def __init__(self):
        super().__init__()

    def run(self):
        self.signal.emit()

要在线程中显示main window,我们需要创建一个qthread来完成这个任务,因为显示window是对qt对象的一种修改,这是不允许的在主线程之外。

正如 join() 方法的文档指出的那样:

join(timeout=None)

Wait until the thread terminates. This blocks the calling thread until the thread whose join() method is called terminates – either normally or through an unhandled exception – or until the optional timeout occurs.

When the timeout argument is present and not None, it should be a floating point number specifying a timeout for the operation in seconds (or fractions thereof). As join() always returns None, you must call is_alive() after join() to decide whether a timeout happened – if the thread is still alive, the join() call timed out.

When the timeout argument is not present or None, the operation will block until the thread terminates.

A thread can be join()ed many times.

join() raises a RuntimeError if an attempt is made to join the current thread as that would cause a deadlock. It is also an error to join() a thread before it has been started and attempts to do so raise the same exception.

(强调我的)

此方法会锁定,直到线程完成执行以阻止 GUI 事件循环的执行,导致它在被冻结后无法响应事件。

解决方法是去掉这个方法:

def start_button_clicked(self):
    self.hide()
    thread = threading.Thread(target=self.check_ULink_status)
    thread.setDaemon(True)
    thread.start()
    <b># thread.join() # remove this line</b>
    self.show()

OP 似乎使用 join() 以便在任务完成时再次显示 window,如果是这样,正确的解决方案是使用信号:

class MainWindow(QMainWindow, Ui_MainWindow):
    <b>finished = QtCore.pyqtSignal()</b>

    def __init__(self):
        super().__init__()
        <b>self.finished.connect(self.show)</b>
        # ...

    # ...

    def check_ULink_status(self):
        # After finishing the task the signal must be emitted
        <b>self.finished.emit()</b>

    # ...

    def start_button_clicked(self):
        self.hide()
        thread = threading.Thread(target=self.check_ULink_status)
        thread.setDaemon(True)
        thread.start()

     # ...