使用tkinter + pyhook时冻结.两个事件循环和多线程 [英] Freeze when using tkinter + pyhook. Two event loops and multithreading

查看:365
本文介绍了使用tkinter + pyhook时冻结.两个事件循环和多线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在python 2.7中编写一个工具,以注册用户按下键盘或鼠标按钮的次数.点击量将显示在屏幕左上方的小黑框中.该程序即使在另一个应用程序处于活动状态时也会注册点击.

I am writing a tool in python 2.7 registering the amount of times the user pressed a keyboard or mouse button. The amount of clicks will be displayed in a small black box in the top left of the screen. The program registers clicks even when another application is the active one.

除了将鼠标移到盒子上之外,它都可以正常工作.然后,鼠标冻结几秒钟,然后程序再次运行.然后,如果我再次将鼠标移到该框上,则鼠标再次冻结,但是这次程序崩溃了.

It works fine except when I move the mouse over the box. The mouse then freezes for a few seconds after which the program works again. If I then move the mouse over the box a second time, the mouse freezes again, but this time the program crashes.

我尝试注释掉pumpMessages(),然后程序正常工作.这个问题看起来很像这个问题 pyhook + tkinter = crash ,但是那里没有解决方案.

I have tried commenting out pumpMessages() and then the program works. The problem looks a lot like this question pyhook+tkinter=crash, but no solution was given there.

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

Other answers has shown that there is a bug with the dll files when using wx and pyhook together in python 2.6. I don't know if that is relevant here.

我自己的想法是,这可能与并行运行的两个事件循环有关.我已经读过tkinter不是线程安全的,但是由于需要同时运行pumpmessages()和mainlooop(),所以我看不到如何使该程序在单个线程中运行.

My own thoughts is that it might have something to do with the two event loops running parallel. I have read that tkinter isn't thread safe, but I can't see how I can make this program run in a single thread since I need to have both pumpmessages() and mainlooop() running.

总结:为什么我的程序在鼠标悬停时会冻结?

To sum it up: Why does my program freeze on mouse over?

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'

推荐答案

从Tkinter需要在主线程中运行并且不能在此范围之外调用的信息中,我找到了一个解决方案:

From the information that Tkinter needs to run in the main thread and not be called outside this thred, I found a solution:

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

My problem was that both PumpMessages and mainLoop needed to run in the main thread. In order to both receive inputs and show a Tkinter label with the amount of clicks I need to switch between running pumpMessages and briefly running mainLoop to update the display.

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

To make mainLoop() quit itself I used:

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

所以100毫秒后root调用了它的quit方法并脱离了自己的主循环

so after 100 milliseconds root calls it's quit method and breaks out of its own main loop

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

To break out of pumpMessages I first found the pointer to the main thread:

mainThreadId = win32api.GetCurrentThreadId()

然后我使用了一个新线程,该线程将WM_QUIT发送到主线程(注意PostQuitMessage(0)仅在主线程中被调用时才有效):

I then used a new thread that sends the WM_QUIT to the main thread (note PostQuitMessage(0) only works if it is called in the main thread):

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

然后可以创建一个在pumpMessagesmainLoop之间更改的while循环,从而更新之间的标签文本.在两个事件循环不再同时运行之后,我没有遇到任何问题:

It was then possible to create a while loop which changed between pumpMessages and mainLoop, updating the labeltext in between. After the two event loops aren't running simultaneously anymore, I have had no problems:

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的信息,以提供

Thank you to Bryan Oakley for information about Tkinter and Boaz Yaniv for providing the information needed to stop pumpMessages() from a subthread

这篇关于使用tkinter + pyhook时冻结.两个事件循环和多线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆