如何在系统托盘中显示反映 NumLk 状态的图标

How to display an icon in the systray reflecting NumLk state

我的计算机无法通过任何方式让我知道我的 NumLk 是打开还是关闭,所以我试图在我的系统托盘中添加一个图标,该图标会根据我的 NumLk 的状态而改变。当我的电脑打开时,这个 .py 将永远是 运行。

到目前为止,我能够混合使用 3 个代码,并且能够在系统托盘中显示图标,但是当 NumLk 的状态发生变化时它不会更新。实际上,如果我按 NumLk 两次,我仍然会得到相同的图标(on 那个)并且我会收到此错误:

QCoreApplication::exec: The event loop is already running
  File "\systray_icon_NumLk_on_off.py", line 21, in on_key_press
    main(on)
  File "\systray_icon_NumLk_on_off.py", line 46, in main
    sys.exit(app.exec_())
SystemExit: -1

我的代码可能不是最好的方法,所以欢迎任何替代方法!到目前为止,这是我得出的结论:

#####get the state of NumLk key
from win32api import GetKeyState 
from win32con import VK_NUMLOCK
#how to use: print(GetKeyState(VK_NUMLOCK))
#source: 

#####Detect if NumLk is pressed 
import pyglet
from pyglet.window import key
window = pyglet.window.Window()
#source: 

on=r'on.png'
off=r'off.png'

@window.event
def on_key_press(symbol, modifiers):
    if symbol == key.NUMLOCK:
        if GetKeyState(VK_NUMLOCK):  
            #print(GetKeyState(VK_NUMLOCK))#should be 0 and 1 but 
            main(on)
        else:
            main(off)
@window.event
def on_draw():
    window.clear()

### display icon in systray
import sys  
from PyQt5 import QtCore, QtGui, QtWidgets
#source:   - add answer 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)

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__':
    pyglet.app.run()

QCoreApplication::exec: The event loop is already running 的原因实际上是因为您尝试启动 app.run() 两次。 Qt 会注意到已经有一个实例 运行 并抛出这个异常。相反,您要做的只是交换已经 运行 实例中的图标。

如果你问我,你这里的主要问题实际上是混合库来解决一个任务。
而是两个任务,但是在图形部分使用 Qt5 很好。

您使用 Pyglet 的方式从一开始就是错误的。
Pyglet 旨在成为一个非常强大和有效的图形库,您可以在其上构建图形引擎。例如,如果您正在制作游戏或 video-player 或其他东西。

您使用 win32api 的方式也是错误的,因为您在图形 window 中使用它,它仅在按下 window.

中的键时检查值

现在,如果您将 win32api 代码移动到线程(准确地说是 QtThread)中,无论您是否在图形 window 中按下按键,都可以检查状态。

import sys  
import win32api
import win32con
from PyQt5 import QtCore, QtGui, QtWidgets
from threading import Thread, enumerate
from time import sleep

class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
    def __init__(self, icon, parent=None):
        QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
        menu = QtWidgets.QMenu(parent)
        exitAction = menu.addAction("Exit")
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(QtWidgets.qApp.quit)
        self.setContextMenu(menu)

class KeyCheck(QtCore.QThread):
    def __init__(self, mainWindow):
        QtCore.QThread.__init__(self)
        self.mainWindow = mainWindow

    def run(self):
        main = None
        for t in enumerate():
            if t.name == 'MainThread':
                main = t
                break

        while main and main.isAlive():
            x = win32api.GetAsyncKeyState(win32con.VK_NUMLOCK)
            ## Now, GetAsyncKeyState returns three values,
            ## 0 == No change since last time
            ## -3000 / 1 == State changed
            ##
            ## Either you use the positive and negative values to figure out which state you're at.
            ## Or you just swap it, but if you just swap it you need to get the startup-state correct.
            if x == 1:
                self.mainWindow.swap()
            elif x < 0:
                self.mainWindow.swap()
            sleep(0.25)

class GUI():
    def __init__(self):
        self.app = QtWidgets.QApplication(sys.argv)

        self.state = True

        w = QtWidgets.QWidget()
        self.modes = {
            True : SystemTrayIcon(QtGui.QIcon('on.png'), w),
            False : SystemTrayIcon(QtGui.QIcon('off.png'), w)
        }

        self.refresh()

        keyChecker = KeyCheck(self)
        keyChecker.start()

        sys.exit(self.app.exec_())

    def swap(self, state=None):
        if state is not None:
            self.state = state
        else:
            if self.state:
                self.state = False
            else:
                self.state = True
        self.refresh()

    def refresh(self):
        for mode in self.modes:
            if self.state == mode:
                self.modes[mode].show()
            else:
                self.modes[mode].hide()

GUI()

请注意,我不经常进行 Qt 编程(每 4 年左右)。
所以这段代码最好是有问题的。您必须在菜单中按 Ctrl+C + 按 "Exit" 才能停止。

老实说,我不想花更多的时间和精力来学习如何在 Qt 中管理线程或如何正确退出应用程序,这不是我的专业领域。但这将为您提供一个粗略的工作示例,说明如何交换下角的图标,而不是尝试 re-instanciate 您所做的 main() 循环。