首页 文章

使用pyHook的Tkinter文本条目挂起GUI窗口

提问于
浏览
0

我有一个Tkinter GUI应用程序,我需要输入文本 . 我不能假设应用程序将具有焦点,所以我实现了pyHook,keylogger风格 .

当GUI窗口没有焦点时,文本输入工作正常,StringVar正确更新 . 当GUI窗口确实有焦点并且我尝试输入文本时,整个事情就会崩溃 .

即,如果我在启动程序后单击控制台窗口或其他任何内容,则文本输入有效 . 如果我尝试立即输入文本(GUI以焦点开始),或者我在任何时候重新聚焦窗口并输入文本,它就会崩溃 .

这是怎么回事?

下面是一个最小的完整可验证示例,用于演示我的意思:

from Tkinter import *
import threading
import time

try:
    import pythoncom, pyHook
except ImportError:
    print 'The pythoncom or pyHook modules are not installed.'

# main gui box
class TestingGUI:
    def __init__(self, root):

        self.root = root
        self.root.title('TestingGUI')

        self.search = StringVar()
        self.searchbox = Label(root, textvariable=self.search) 
        self.searchbox.grid()

    def ButtonPress(self, scancode, ascii):
        self.search.set(ascii)

root = Tk()
TestingGUI = TestingGUI(root)

def keypressed(event):
    key = chr(event.Ascii)
    threading.Thread(target=TestingGUI.ButtonPress, args=(event.ScanCode,key)).start()
    return True

def startlogger():
    obj = pyHook.HookManager()
    obj.KeyDown = keypressed
    obj.HookKeyboard()
    pythoncom.PumpMessages()

# need this to run at the same time
logger = threading.Thread(target=startlogger)
# quits on main program exit
logger.daemon = True
logger.start()

# main gui loop
root.mainloop()

1 回答

  • 2

    我修改了问题中给出的源代码(和另一个),以便pyHook相关的回调函数将键盘事件相关数据发送到队列 . GUI对象通知事件的方式可能看起来不必要地复杂化 . 试图在 keypressed 中调用 root.event_generate 似乎挂了 . 在 keypressed 中调用时, threading.Eventset 方法似乎也会造成麻烦 .

    调用 keypressed 的上下文可能背后的麻烦 .

    from Tkinter import *
    import threading
    
    import pythoncom, pyHook
    
    from multiprocessing import Pipe
    import Queue
    import functools
    
    class TestingGUI:
        def __init__(self, root, queue, quitfun):
            self.root = root
            self.root.title('TestingGUI')
            self.queue = queue
            self.quitfun = quitfun
    
            self.button = Button(root, text="Withdraw", command=self.hide)
            self.button.grid()
    
            self.search = StringVar()
            self.searchbox = Label(root, textvariable=self.search)
            self.searchbox.grid()
    
            self.root.bind('<<pyHookKeyDown>>', self.on_pyhook)
            self.root.protocol("WM_DELETE_WINDOW", self.on_quit)
    
            self.hiding = False
    
        def hide(self):
            if not self.hiding:
                print 'hiding'
                self.root.withdraw()
                # instead of time.sleep + self.root.deiconify()
                self.root.after(2000, self.unhide)
                self.hiding = True
    
        def unhide(self):
            self.root.deiconify()
            self.hiding = False
    
        def on_quit(self):
            self.quitfun()
            self.root.destroy()
    
        def on_pyhook(self, event):
            if not queue.empty():
                scancode, ascii = queue.get()
                print scancode, ascii
                if scancode == 82:
                    self.hide()
    
                self.search.set(ascii)
    
    root = Tk()
    pread, pwrite = Pipe(duplex=False)
    queue = Queue.Queue()
    
    def quitfun():
        pwrite.send('quit')
    
    TestingGUI = TestingGUI(root, queue, quitfun)
    
    def hook_loop(root, pipe):
        while 1:
            msg = pipe.recv()
    
            if type(msg) is str and msg == 'quit':
                print 'exiting hook_loop'
                break
    
            root.event_generate('<<pyHookKeyDown>>', when='tail')
    
    # functools.partial puts arguments in this order
    def keypressed(pipe, queue, event):
        queue.put((event.ScanCode, chr(event.Ascii)))
        pipe.send(1)
        return True
    
    t = threading.Thread(target=hook_loop, args=(root, pread))
    t.start()
    
    hm = pyHook.HookManager()
    hm.HookKeyboard()
    hm.KeyDown = functools.partial(keypressed, pwrite, queue)
    
    try:
        root.mainloop()
    except KeyboardInterrupt:
        quit_event.set()
    

相关问题