2016-06-19 4 views
0

У меня есть приложение GUI Tkinter, в которое мне нужно ввести текст. Я не могу предположить, что приложение будет иметь фокус, поэтому я реализовал pyHook, keylogger-style.Tkinter текстовая запись с pyHook висит окно GUI

Когда окно GUI не имеет фокуса, текстовая запись работает нормально, а обновления StringVar корректно отображаются. Когда в окне GUI есть, у меня есть фокус, и я пытаюсь ввести текст, все дело падает.

Т.е., если я нажимаю на окно консоли или что-то еще после запуска программы, работает текстовая запись. Если я сразу же попробую ввести текст (графический интерфейс начнется с фокуса), или я переориентирую окно в любой момент и введите текст, он сработает.

Что происходит?

Ниже приведен минимальный полный проверяемым пример, чтобы продемонстрировать, что я имею в виду:

from Tkinter import * 
import threading 
import time 

try: 
    import pythoncom, pyHook 
except ImportError: 
    print 'The pythoncom or pyHook modules are not installed.' 

# main gui box 
class TestingGUI: 
    def __init__(self, root): 

     self.root = root 
     self.root.title('TestingGUI') 

     self.search = StringVar() 
     self.searchbox = Label(root, textvariable=self.search) 
     self.searchbox.grid() 

    def ButtonPress(self, scancode, ascii): 
     self.search.set(ascii) 

root = Tk() 
TestingGUI = TestingGUI(root) 

def keypressed(event): 
    key = chr(event.Ascii) 
    threading.Thread(target=TestingGUI.ButtonPress, args=(event.ScanCode,key)).start() 
    return True 

def startlogger(): 
    obj = pyHook.HookManager() 
    obj.KeyDown = keypressed 
    obj.HookKeyboard() 
    pythoncom.PumpMessages() 

# need this to run at the same time 
logger = threading.Thread(target=startlogger) 
# quits on main program exit 
logger.daemon = True 
logger.start() 

# main gui loop 
root.mainloop() 
+1

1. Если вы хотите отвечать на запросы пользователей, используйте комментарии; мне повезло, что я вернулся и посмотрел. 2. Если вы хотите избежать этого, потратьте больше времени на написание описательного названия, а не на «странность» *, которая никому не помогает. Тем не менее, я приношу свои извинения за то, что они запутали их. – jonrsharpe

+1

1. Я не знал, что это уведомило вас, увидев, что вы не комментировали или не взаимодействовали с моим сообщением. 2. Чтение только второго абзаца показало бы, что это разные вопросы, или вы основывали свой дубликат исключительно на основе названия? Я использую неопределенный термин, как странность, потому что я не знаю, что происходит. – heidi

+0

Темы и tkinter не очень хорошо смешиваются, см. Например http://stackoverflow.com/a/10556698/5781248 –

ответ

1

Я изменил исходный код, приведенный в вопросе (и другой), так что соответствующая функция обратного вызова pyHook отправляет событие клавиатуры связанных с данными в очередь . Способ, которым объект GUI уведомляется о событии, может выглядеть излишне сложным. Попытка позвонить root.event_generate в keypressed, казалось, повесил. Также set метод threading.Event, казалось, вызывал проблемы при вызове в keypressed.

Контекст, в котором вызывается keypressed, возможно, находится за проблемой .

from Tkinter import * 
import threading 

import pythoncom, pyHook 

from multiprocessing import Pipe 
import Queue 
import functools 

class TestingGUI: 
    def __init__(self, root, queue, quitfun): 
     self.root = root 
     self.root.title('TestingGUI') 
     self.queue = queue 
     self.quitfun = quitfun 

     self.button = Button(root, text="Withdraw", command=self.hide) 
     self.button.grid() 

     self.search = StringVar() 
     self.searchbox = Label(root, textvariable=self.search) 
     self.searchbox.grid() 

     self.root.bind('<<pyHookKeyDown>>', self.on_pyhook) 
     self.root.protocol("WM_DELETE_WINDOW", self.on_quit) 

     self.hiding = False 

    def hide(self): 
     if not self.hiding: 
      print 'hiding' 
      self.root.withdraw() 
      # instead of time.sleep + self.root.deiconify() 
      self.root.after(2000, self.unhide) 
      self.hiding = True 

    def unhide(self): 
     self.root.deiconify() 
     self.hiding = False 

    def on_quit(self): 
     self.quitfun() 
     self.root.destroy() 

    def on_pyhook(self, event): 
     if not queue.empty(): 
      scancode, ascii = queue.get() 
      print scancode, ascii 
      if scancode == 82: 
       self.hide() 

      self.search.set(ascii) 

root = Tk() 
pread, pwrite = Pipe(duplex=False) 
queue = Queue.Queue() 

def quitfun(): 
    pwrite.send('quit') 

TestingGUI = TestingGUI(root, queue, quitfun) 

def hook_loop(root, pipe): 
    while 1: 
     msg = pipe.recv() 

     if type(msg) is str and msg == 'quit': 
      print 'exiting hook_loop' 
      break 

     root.event_generate('<<pyHookKeyDown>>', when='tail') 

# functools.partial puts arguments in this order 
def keypressed(pipe, queue, event): 
    queue.put((event.ScanCode, chr(event.Ascii))) 
    pipe.send(1) 
    return True 

t = threading.Thread(target=hook_loop, args=(root, pread)) 
t.start() 

hm = pyHook.HookManager() 
hm.HookKeyboard() 
hm.KeyDown = functools.partial(keypressed, pwrite, queue) 

try: 
    root.mainloop() 
except KeyboardInterrupt: 
    quit_event.set() 
+0

Спасибо! Это сработало. Я делал то же самое, что и в OP на linux, и он работал отлично, поэтому я предположил, что он будет работать и на окнах. Еще одна вещь: я замечаю, что вы не использовали PumpMessages - я думал, что это необходимо? – heidi

+1

@heidi tkinter mainloop вызывает [PeekMessage] (https://msdn.microsoft.com/en-us/library/windows/desktop/ms644943 (v = vs.85) .aspx) в какой-то момент, который, вероятно, достаточно для запуска крючки, установленные pyHook, как в [ответе] (http://stackoverflow.com/a/7460728) –

Смежные вопросы