Extract binding event name from tkinter's event

I’ve created a KeyBinder object which is basically a button that, when pressed, will listen for a keypress event and then capture the next keypress. Now I’ve modified it to test what info inside the event object i have to bind to the root window to actually use the keybind:

import tkinter as tk

class KeyBinder(tk.Button):
    key_code: str = None
    bind_list: set[int] = {}
    def __init__(self,bind_list=None,*args,**kwargs):
        super().__init__(*args,**kwargs)
        #self.config(command=self.folder_select())
        self.key_code = None
        if "text" not in kwargs: self.config(text="Choose key")
        self.bind("<1>",lambda event:self.start_keybind())
    def start_keybind(self):
        self.winfo_toplevel().bind("<Key>",self.bind_key)
        self.config(text="Press a key...")
    def bind_key(self,event:tk.Event):
        if event.keysym == "Escape":
            self.key_code = None
            self.config(text="Choose key")
        elif not self.bind_list or event.keycode not in self.bind_list:
            self.key_code = event.keysym
            self.config(text=event.keysym)
        else:
            self.key_code = None
            self.config(text="Key already bound")
        self.winfo_toplevel().unbind("<Key>")

        self.winfo_toplevel().bind(self.key_code,add_a)

###########

def add_a(_):
    l = label
    text = l.cget("text")
    l.config(text+"a")

############

window = tk.Tk()
btn = KeyBinder()
btn.pack()
label = tk.Label(text="b")
label.pack()

window.mainloop()

I’ve tried using event.key, .keysym, .keycode and converting all of them into strings encapsulated in < > as the first window.bind() argument, but the bound function doesn’t fire in any of those cases.
What’s the intended way to capture user input and use it as a keybind?

I think that there’s an error in line 5 of your code…

I changed this…

bind_list: set[int] = {}

… to this…

bind_list: set([int]) = {}

… and the code seems to work, but I’m unsure if it’s doing what you want.

1 Like

I just realized that bind events are case sensitive (e.g. Right vs space ), so to avoid having to check for special cases I’ll just restrict the binding to letters and numbers.

Regarding your correction: does type hinting syntax have an effect at runtime?

Humm, not sure how to answer this, as your skill set is above mine. All I know is that without my mod, I couldn’t get you code to run; it through an error…

Traceback (most recent call last):
  File "./untitled-2.py", line 5, in <module>
    class KeyBinder(tk.Button):
  File "./untitled-2.py", line 7, in KeyBinder
    bind_list: set[int] = {}
TypeError: 'type' object is not subscriptable

I hope this is of help.

Indeed, it is not. I suggest you read PEP 526 to familiarize yourself with the typing syntax for variable annotations, as your suggestion indicates you are almost certainly not unfamiliar with it.

Specifically, the annotation set[int] annotates the variable bind_list as being a set composed of int members (set is a generic collection, which is parameterized by the type of its elements). The proposed alteration actually instantiates a set composed of one member, the int type, which of course is not what is intended.

You are running into an issue because you are running the code on Python <3.9, which means that the builtin set cannot be subscripted with a type for use in type checking (rather, you’d have to from typing import Set and use that instead for annotation purposes), and without using from __future__ import annotations, which work around that issue by treating annotations as strings rather than evaluating them at runtime.

To avoid this, instead of mangling the type hints, just add from __future__ import annotations at the top of the module (assuming you’re using a supported Python version, i.e. 3.7+) or upgrade to Python 3.9+.

In this case, yes, because from __future__ import annotations isn’t used. See PEP 563 for more details.

1 Like