3

ユーザーがキーボードまたはマウスボタンを押した回数を登録するPython 2.7でツールを作成しています。クリック数は、画面左上の小さな黒いボックスに表示されます。プログラムは、別のアプリケーションがアクティブな場合でもクリックを登録します。

ボックスの上にマウスを移動する場合を除いて、正常に動作します。その後、マウスが数秒間フリーズした後、プログラムが再び動作します。次にマウスをボックスの上にもう一度移動すると、マウスは再びフリーズしますが、今度はプログラムがクラッシュします。

pumpMessages() をコメントアウトしてみましたが、プログラムは機能します。問題はこの質問pyhook+tkinter=crashによく似ていますが、解決策はありませんでした。

他の回答は、python 2.6 で wx と pyhook を一緒に使用すると、dll ファイルにバグがあることを示しています。それがここに関連しているかどうかはわかりません。

私自身の考えでは、2 つのイベント ループが並行して実行されていることに関係があるのではないかと考えています。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'
4

3 に答える 3

1

Tkinter はメイン スレッドで実行する必要があり、このスレッドの外部では呼び出されないという情報から、解決策を見つけました。

私の問題は、 と の両方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その後、との間で変化する while ループを作成し、その間mainLoopに labeltext を更新することができました。2 つのイベント ループが同時に実行されなくなった後、問題はなくなりました。

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 に関する情報を提供してくれた Bryan Oakley と、サブスレッドからの pumpMessages() を停止するために必要な情報を提供してくれた Boaz Yaniv に感謝します。

于 2012-09-05T23:03:23.060 に答える
0

Tkinter は、メイン スレッド以外のスレッドから実行するようには設計されていません。GUI をメイン スレッドに配置し、呼び出しを別のスレッドに配置すると役立つ場合がありPumpMessagesます。ただし、他のスレッドから Tkinter 関数を呼び出さないように注意する必要があります (おそらく を除くevent_generate)。

于 2012-09-05T11:19:26.057 に答える