0

Can you please tell me if there's a convenient way to have a bind only on "significant" keys with tk/tkinter?

So, the situation is I have a bind on a tk.Entry (because I need a callback to trigger while keys are being pressed), like this:

myEntry.bind('<Key>', ...)

Right now, the bind callback is triggered whatever key is pressed (even if it is the shift!) but I'd want it to trigger only on "significant" keys... By "significant" I mean every key that involve changes on the text in the Entry, so "significant" keys are for sure letters, numbers, symbols and the backspace or delete keys, but NOT arrow keys, home, end, pgUp/Down or also the tab, caps-lock, shift, ctrl, etc... (I'm still thinking if I will need it to trigger on return key or not but that's not a problem, because if I need it too I could add a specific bind on it or otherwise have it ignored in the callback later)

I don't know if perhaps is there something different from <Key> I could bind to get what I need, is it? Otherwise, if not, I know I can get which key was pressed looking at event keycode... is that the way to go? If yes, can you suggest me some suitable keycode interval please?

Thank you

danicotra
  • 1,333
  • 2
  • 15
  • 34
  • `event.char` will give You the key – Matiiss Apr 21 '21 at 18:55
  • @Matiiss yes, I think it works similarly to event.keycode but, instead of a numeric code, it gives the key name (string)... Then, if I must go that way, event.keycode is perhaps more suitable to filter I guess... – danicotra Apr 21 '21 at 18:56
  • not really, is it tho? because wouldn't they change based on system? – Matiiss Apr 21 '21 at 18:57
  • Does this answer your question? [Python tkinter text modified callback](https://stackoverflow.com/questions/40617515/python-tkinter-text-modified-callback) Binding on key events isn't going to see every possible change - what if the user pasted text using only the mouse? – jasonharper Apr 21 '21 at 19:01
  • @Matiiss that's a good point, I never thought keycodes might be different depending on the system (thought they were cross-platform so far)... Can you provide me a link to reference about it? (Thanks) – danicotra Apr 21 '21 at 19:02
  • I have no reference it is just what I thought about – Matiiss Apr 21 '21 at 19:05
  • @jasonharper it could quite well be if I can use that createcommand/proxy stuff on Entry besides of Text... I'm giong to try! Thank you – danicotra Apr 21 '21 at 19:07
  • Entry is a lot easier to detect changes - attach it to a StringVar, put a watch on that var. – jasonharper Apr 21 '21 at 19:27
  • @jasonharper How to paste text with mouse? Without making it a feature to right click and choose paste? – Delrius Euphoria Apr 21 '21 at 19:36
  • To start with, check if what all keys you need to include and dont include, then check which is more. Then use that inside the `if` as shown below with appropriate `in` or `not in`. – Delrius Euphoria Apr 21 '21 at 19:44
  • 1
    _"because I need a callback to trigger while keys are being pressed"_ - out of curiosity, do you need a binding when a key is pressed, or is the real problem that you need a callback to run whenever the contents of the Entry changes? If the latter, what is the callback doing? Is it doing validation of the input, or something else? – Bryan Oakley Apr 21 '21 at 19:47
  • @jasonharper sorry for delay, I tried the suggested answer and it works... things seems like slowing down a bit if compared with the code I was using before (despite that triggered even on shift) but I think I could live with that in the end... Can you please provide an example about the StringVar solution you have in mind? (As an answer maybe?) Thank you – danicotra Apr 21 '21 at 19:57
  • @BryanOakley Hello Bryan, I'm "real-time"-filtering a tk.Listbox upon the input in the tk.Entry. I was using after() to delay a little the callback, just trying to reduce the number of computations thinking of a person typing several keys in rapid succession: in the real code for first I compare the previous input with the current one before going on... – danicotra Apr 21 '21 at 20:05
  • In that case, adding a trace on a `StringVar` associated with the `Entry` seems like a better solution, since the trace is only called when the value is changed. – Bryan Oakley Apr 21 '21 at 20:27
  • @BryanOakley Ok, got it. I'll follow that way then. If you put this in form of an answer I'll be happy to accept it. Thank you – danicotra Apr 21 '21 at 20:41

2 Answers2

2

In a comment, you say you are doing real-time filtering of a listbox. For that, it would arguably be better to set a trace on a variable rather than setting a binding on the entry widget. The trace will only be called when the value changes, so you don't have to distinguish which key triggered the change.

Here's a simple example that illustrates the concept:

import tkinter as tk

widgets = [w.__name__ for w in tk.Widget.__subclasses__()]

root = tk.Tk()
entryvar = tk.StringVar()
entry = tk.Entry(root, textvariable=entryvar)
listbox = tk.Listbox(root)
entry.pack(side="top", fill="x")
listbox.pack(side="bottom", fill="both", expand=True)
listbox.insert("end", *sorted(widgets))

after_id = None

def filter_changed(*args):
    global after_id
    if after_id:
        root.after_cancel(after_id)

    # delay actual filtering slightly, in case the user is typing fast.
    after_id = root.after(500, filter_listbox)

def filter_listbox(*args):
    pattern = entryvar.get()
    filtered_widgets = [w for w in widgets if w.startswith(pattern) ]
    listbox.delete(0, "end")
    listbox.insert("end", *filtered_widgets)

entryvar.trace("wu", filter_changed)

root.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
1

Here is my solution, it uses event.char and evaluates against a string with characters (can add more characters if needed):

from tkinter import Tk, Entry


string = r"""0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"""


def key_press(event):
    if event.char and event.char in string:
        print(event.char)


root = Tk()

entry = Entry(root)
entry.pack()
entry.bind('<Key>', key_press)

root.mainloop()
Matiiss
  • 5,970
  • 2
  • 12
  • 29
  • That's interesting but also backspace and delete keys need to be accepted. Other than that, I don't know what happens when you paste over contents (or part of them) – danicotra Apr 21 '21 at 19:27
  • @danicotra What all are the keys you dont want then – Delrius Euphoria Apr 21 '21 at 19:33
  • What is the purpose of two `event.char`? `if event.char and event.char`. – Delrius Euphoria Apr 21 '21 at 19:34
  • @CoolCloud sometimes there is no char but neither is there an error so I check if the `.char` even exists, it prints blank if the first check is not in place – Matiiss Apr 21 '21 at 19:40
  • 1
    @danicotra pasting contents realistically only triggers `ctrl + v` so nothing should be printed, about the backspace, I guess You could use either `.keysym` or `.keycode` and put additional checks but those buttons are only a few so the majority will be checked in an easier way – Matiiss Apr 21 '21 at 19:41
  • Thank you as well Matiiss, your approach might came handy for some other circumstances in the future – danicotra Apr 21 '21 at 20:48