2

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.

Andrze
  • 23
  • 1
  • 3
  • 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('', on_press)`, `root.bind('', on_release)` – furas Jul 03 '19 at 14:59
  • tkinter example using `root.bind()` to move paddle (up/down) on canvas: https://github.com/furas/python-examples/blob/master/tkinter/event-keys-move-pad-on-canvas/example-1.py – furas Jul 03 '19 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? – Bryan Oakley Jul 03 '19 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? – Andrze Jul 04 '19 at 20:54

2 Answers2

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()
furas
  • 134,197
  • 12
  • 106
  • 148
  • 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. – Jonathan Allin Jul 28 '21 at 20:54
  • 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` – furas Jul 28 '21 at 23:21
  • 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) – Jonathan Allin Jul 30 '21 at 18:59
  • 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. – furas Jul 31 '21 at 06:35
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)

crissal
  • 2,547
  • 7
  • 25