首页 文章

使用tkinter pyhook时冻结 . 两个事件循环和多线程

提问于
浏览
3

我在python 2.7中编写了一个工具,用于记录用户按下键盘或鼠标按钮的次数 . 点击量将显示在屏幕左上方的一个小黑框中 . 即使其他应用程序是活动应用程序,该程序也会记录点击 .

它工作正常,除非我将鼠标移到盒子上 . 然后鼠标冻结几秒钟,之后程序再次运行 . 如果我再次将鼠标移到盒子上,鼠标会再次冻结,但这次程序崩溃了 .

我试过注释掉pumpMessages(),然后程序就可以了 . 问题看起来很像这个问题pyhook+tkinter=crash,但没有给出解决方案 .

其他答案表明,在python 2.6中使用wx和pyhook时,dll文件存在错误 . 我不知道这是否相关 .

我自己的想法是它可能与两个并行运行的事件循环有关 . 我已经读过tkinter不是线程安全的,但我看不出如何让这个程序在一个线程中运行,因为我需要运行pumpmessages()和mainlooop() .

总结一下:为什么我的程序冻结鼠标悬停?

import pythoncom, pyHook, time, ctypes, sys
from Tkinter import *
from threading import Thread

print 'Welcome to APMtool. To exit the program press delete'

## Creating input hooks

#the function called when a MouseAllButtonsUp event is called
def OnMouseUpEvent(event): 
    global clicks
    clicks+=1
    updateCounter()
    return True

#the function called when a KeyUp event is called
def OnKeyUpEvent(event): 
    global clicks
    clicks+=1
    updateCounter()
    if (event.KeyID == 46):
        killProgram()
    return True


hm = pyHook.HookManager()# create a hook manager

# watch for mouseUp and keyUp events
hm.SubscribeMouseAllButtonsUp(OnMouseUpEvent)
hm.SubscribeKeyUp(OnKeyUpEvent)

clicks = 0

hm.HookMouse()# set the hook
hm.HookKeyboard()

## Creating the window
root = Tk()
label = Label(root,text='something',background='black',foreground='grey')
label.pack(pady=0) #no space around the label
root.wm_attributes("-topmost", 1) #alway the top window
root.overrideredirect(1) #removes the 'Windows 7' box around the label

## starting a new thread to run pumMessages() and mainloop() simultaniusly
def startRootThread():
    root.mainloop()

def updateCounter():
    label.configure(text=clicks)

def killProgram():
    ctypes.windll.user32.PostQuitMessage(0) # stops pumpMessages
    root.destroy() #stops the root widget
    rootThread.join()
    print 'rootThread stopped'



rootThread = Thread(target=startRootThread)
rootThread.start()

pythoncom.PumpMessages() #pump messages is a infinite loop waiting for events

print 'PumpMessages stopped'

3 回答

  • 1

    我用多处理解决了这个问题:

    • 主进程处理GUI(MainThread)和消耗来自第二进程的消息的线程

    • 子进程挂钩所有鼠标/键盘事件并将它们推送到主进程(通过Queue对象)

  • 1

    根据Tkinter需要在主线程中运行的信息而不是在这个thred之外调用,我找到了一个解决方案:

    我的问题是 PumpMessagesmainLoop 都需要在主线程中运行 . 为了接收输入并显示Tkinter标签,我需要在运行 pumpMessages 和短暂运行 mainLoop 以更新显示之间切换 .

    为了让 mainLoop() 退出,我使用了:

    after(100,root.quit()) #root is the name of the Tk()
    mainLoop()
    

    所以在100毫秒后 root 调用它的 quit 方法并打破它自己的主循环

    为了打破pumpMessages,我首先找到了指向主线程的指针:

    mainThreadId = win32api.GetCurrentThreadId()
    

    然后我使用了一个新的线程将 WM_QUIT 发送到主线程(注意 PostQuitMessage(0) 只有在主线程中调用它才有效):

    win32api.PostThreadMessage(mainThreadId, win32con.WM_QUIT, 0, 0)
    

    然后可以创建一个在 pumpMessages 和_2434490之间更改的while循环,更新之间的labeltext . 在两个事件循环不再同时运行之后,我没有遇到任何问题:

    def startTimerThread():
        while True:
            win32api.PostThreadMessage(mainThreadId, win32con.WM_QUIT, 0, 0)
            time.sleep(1)
    
    mainThreadId = win32api.GetCurrentThreadId()
    timerThread = Thread(target=startTimerThread)
    timerThread.start()
    
    while programRunning:
        label.configure(text=clicks)
        root.after(100,root.quit)
        root.mainloop()
        pythoncom.PumpMessages()
    

    感谢Bryan Oakley提供有关Tkinter和Boaz Yaniv的信息,以便提供所需的信息stop pumpMessages() from a subthread

  • 0

    Tkinter不是设计为从主要线程以外的任何线程运行 . 将GUI放在主线程中并将调用 PumpMessages 放在一个单独的线程中可能会有所帮助 . 虽然你必须要小心,不要从另一个线程调用任何Tkinter函数(除了 event_generate 除外) .

相关问题