3

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

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

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

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

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

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

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'
4

3 回答 3

1

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

我的问题是两者都PumpMessages需要mainLoop在主线程中运行。为了接收输入并显示带有点击量的 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和之间变化的 while 循环mainLoop,更新其间的 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 提供从子线程停止 pumpMessages()所需的信息

于 2012-09-05T23:03:23.060 回答
1

我已经通过多处理解决了这个问题:

  1. 主进程处理 GUI (MainThread) 和一个从第二个进程消耗消息的线程

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

于 2015-12-16T12:13:34.997 回答
0

Tkinter 的设计目的不是从除主线程之外的任何线程运行。将 GUI 放在主线程中并将调用PumpMessages放在单独的线程中可能会有所帮助。尽管您必须小心,不要从其他线程调用任何 Tkinter 函数(可能除外event_generate)。

于 2012-09-05T11:19:26.057 回答