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

时间:2017-01-22 12:07:56

标签: windows-7 python-3.5 keydown pyglet systray

如果我的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: http://stackoverflow.com/questions/21160100/python-3-x-getting-the-state-of-caps-lock-num-lock-scroll-lock-on-windows

#####Detect if NumLk is pressed 
import pyglet
from pyglet.window import key
window = pyglet.window.Window()
#source: http://stackoverflow.com/questions/28324372/detecting-a-numlock-capslock-scrlock-keypress-keyup-in-python

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: http://stackoverflow.com/questions/893984/pyqt-show-menu-in-a-system-tray-application  - 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()

1 个答案:

答案 0 :(得分:1)

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

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

你开始使用Pyglet的方式是错误的。
Pyglet旨在成为一个功能强大且高效的图形库,您可以在其上构建图形引擎。例如,如果你正在制作游戏或视频播放器等等。

你使用win32api的方式也是错误的,因为你在一个图形窗口中使用它,只在该窗口内按下一个键时检查该值。

现在,如果您将win32api代码移动到一个线程(确切地说是一个QtThread),无论您是否在图形窗口内按下了键,都可以检查状态。

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 +按菜单中的“退出”才能停止。

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