3

I am a teacher. I teach math, but since education is facing human resources crisis, I have some additional duties. I teach kids a bit of programming, they do quite well. Now I'd like to make with them a snake game, but I have a problem achieving multithreading in my GUI app.

I found similar cases but no solutions. Like here: Using the keyboard to move the character in a maze and here: Tkinter.Tk() and threading

def on_press(key):
    print('{0} pressed'.format(key))

def on_release(key):
    if key == Key.esc:
        return False

with Listener(on_press=on_press, on_release=on_release) as listener:
    listener.join()

root = Tk()
root.mainloop()

I expected the window to run simultaneously with the listener. instead, my code listens to the keyboard and then (after I shoot the listener down) pops the window up. it reverses when I call the listener after calling the mainloop, then first the window appears and after I shoot it down the listener is starting to work.

4
  • I don't know why you need Listener and threads to create game in Tkinter. You could do it without threads (ie. using tk.after()) and without Listener using root.bind('<KeyPress>', on_press), root.bind('<KeyRelease>', on_release) Commented Jul 3, 2019 at 14:59
  • tkinter example using root.bind() to move paddle (up/down) on canvas: github.com/furas/python-examples/blob/master/tkinter/… Commented Jul 3, 2019 at 15:03
  • 1
    Why aren't you using tkinter's ability to listen to events? Why do you need to use the Listener class? Commented Jul 3, 2019 at 15:15
  • didn't know about tkinter solutions. you are pretty awesome, thx. everything works now. but, just to be sure, since you wrote "You could do it without threads ", doesn't bind create a thread? Commented Jul 4, 2019 at 20:54

2 Answers 2

3

You don't need Listener in tkinter. You can use root.bind to assign function to events press and release.

from tkinter import *

def on_press(event):
    #print('on_press: event:', event)
    #print('on_press: keysym:', event.keysym)
    print('{0} pressed'.format(event.keysym))

def on_release(event):
    #print('on_release: event:', event)
    #print('on_release: keysym:', event.keysym)
    print('{0} release'.format(event.keysym))

    if event.keysym == 'Escape':
         print("exist program")
         root.destroy()

root = Tk()

root.bind('<KeyPress>', on_press)
root.bind('<KeyRelease>', on_release)

root.mainloop()

You can also assign function to every key separatelly

from tkinter import *

def on_escape(event):
    print("exist program")
    root.destroy()

root = Tk()

root.bind('<Escape>', on_escape)
#root.bind('<KeyPress-Escape>', on_press_escape)
#root.bind('<KeyRelease-Escape>', on_release_escape)

root.mainloop()

Keysyms in Tcl/Tk documentation: https://www.tcl.tk/man/tcl8.4/TkCmd/keysyms.htm


BTW:

If you want to run tkinter and pynput at the same time then you have to do it before join()

with Listener(on_press=on_press, on_release=on_release) as listener:

    root = Tk()
    root.mainloop()

    #listener.stop()
    listener.join()

or

listener = Listener(on_press=on_press, on_release=on_release)
listener.start()

root = Tk()
root.mainloop()

#listener.stop()
listener.join()
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks furas. This is so much simpler that other approaches I'd been trying to get working. pygame has a nice keyboard event handler but there's big impedance mismatch between pygame and tkinter. pynput looked promising, but again difficult to use. The only minor problem with your approach is that if a key is held down a stream of KeyPress events are generated.
you have the same minor problem with pynput Listener - it also get all keys and you have to filter it. And as I know this stream is generated by system, not by tkinter or pynput
I suspect I've now run into an issue with the way the hardware scans the keyboard. I want to detect when multiple keys are pressed, however I can only reliably pick up two simultaneously pressed keys: I can pick up four or five keys at the left or right end of the keyboard (eg Q, W, E, R, and T), but only two in the middle (eg G and H)
as I know some keyboard can't send more keys. You can eventually use dict {"Q": False, "W": False, ...}` and in on_press get pressed key and set it True in dictionary and check if all needed keys are True, and in on_release set False in dictionary.
2

Listener is a thread, so if you join it your main thread will wait until its end to continue processing.

You can just create a Listener object without the with statement and it will run along the main thread (until a callback function will return False)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.