使用tkinter + pyhook时冻结.两个事件循环和多线程 [英] Freeze when using tkinter + pyhook. Two event loops and multithreading
问题描述
我正在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:
我的问题是PumpMessages
和mainLoop
都需要在主线程中运行.为了同时接收输入并显示带有点击量的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)
然后可以创建一个在pumpMessages
和mainLoop
之间更改的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屋!