2012-09-05 59 views
3

我正在寫python 2.7中的一個工具,記錄用戶按下鍵盤或鼠標按鈕的次數。點擊量將顯示在屏幕左上方的小黑框中。即使另一個應用程序是活動應用程序,程序也會註冊點擊。使用tkinter + pyhook時凍結。兩個事件循環和多線程

它工作正常,除非我將鼠標移到框上。然後鼠標凍結幾秒鐘後,程序再次運行。如果我再次將鼠標移到框上,鼠標再次凍結,但是這次程序崩潰了。

我已經嘗試註釋掉pumpMessages(),然後程序工作。這個問題看起來很像這個問題pyhook+tkinter=crash,但沒有給出解決方案。

其他答案表明,在python 2.6中使用wx和pyhook時,dll文件存在一個bug。我不知道這是否與此有關。

我自己的想法是,它可能與兩個並行運行的事件循環有關。我已經讀過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' 

回答

0

Tkinter的設計不是從主要的任何線程運行。它可能有助於將GUI放在主線程中,並將呼叫置於一個單獨的線程中,即PumpMessages。雖然你必須小心,不要從其他線程調用任何Tkinter函數(除了可能的event_generate)。

+0

確定,這可能是它。我已經嘗試將pumpmessages()放入另一個線程,但它不起作用。我發現某處pumpmessages()也只能在主線程中運行。任何建議的替代gui呢?還是另一種解決方法? – Thorbjorn

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) 

就在那時可以創建一個while循環,其pumpMessagesmainLoop之間變化,在更新之間的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() 

謝謝布賴恩·奧克利有關的Tkinter和波阿斯的Yaniv信息提供所需stop pumpMessages() from a subthread

1

我的信息VE解決了這個問題與多處理:

  1. 主過程處理GUI(MainThread)和從所述第二過程

  2. 消耗消息的線索個
  3. 子進程鉤所有鼠標/鍵盤事件,並將它們推到主處理(通過隊列對象)

+0

你是如何檢查主線程中的隊列的?它阻止了qui嗎? –