使用 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()
# ...
我正在用 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()
# ...