Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to identify non-printable KeyPress events in Tkinter

Tags:

python

tkinter

tk

I'm making an app in Tkinter using multiple Text widgets, and I'm working on undo/redo functionality, which is triggered by a KeyPress event. (I'm not using the text widget's built-in undo stack because I have non-tkinter objects which can also be undone/redone and I want to keep everything in one stack)

Before I began, I read tutorials and documentation (e.g. http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm) which gave me the impression event.char would be empty or None if the key pressed was a 'special' key rather than an alphanumeric/punctuation key.

This is not actually the case. Pressing Backspace on my keyboard (I'm using a Macbook Air running El Capitan) returns an event.char of \x7f.

Reading more about Tcl/TK, I also learned the list of X keysyms are not consistent from platform to platform, and that e.g. on my Mac, the Shift keys apparently have a keysym code of 0. This means my application interpreted unprintable characters like Delete or the Shift key as printable characters, which is messing with my implementation.

I'll handle the common unprintable-key KeyPress events separately anyway, but I'm still concerned about unexpected keyboard event behaviour for two reasons: 1) my program is intended to be multi-platform, and 2) there is a high chance this program will be used by people using non-US keyboards.

My current solution, using this as a reference, is to check event.keysym_num to see if it is within the range of keysym numbers/keycodes used by printable characters.

Here is example code:

from tkinter import *
from tkinter import ttk

root = Tk()

textbox = Text(root, width=60, height=3)
textbox.grid(sticky=(N, S, E, W))

def KeyboardEvent(event):
    if event.keysym_num > 0 and event.keysym_num < 60000:
        print('This is a printable key. The character is: %r keysym: %r' % \
            (event.char, event.keysym_num))
    else:
        print('This key is unprintable. The character is: %r keysym: %r' % \
            (event.char, event.keysym_num))
textbox.bind('<KeyPress>', KeyboardEvent)
root.mainloop() 

My questions:

  1. Will my current method work, or are there non-printable keys that will still fall through?
  2. Is there a better way to tell non-printable key events apart from printable key events in Tkinter/Python? (I considered using unicodecategory or curses.ascii.isprint() but this check occurs every time the user presses a key so these seemed like overkill)
  3. Are there any more gotchas with KeyPress events, particularly inconsistent behaviour between Windows/Mac/Linux, that I should be aware of? (note: I'm already aware of issues with whitespace characters such as tabs and carriage returns)

I'm using Python 3 (installed with Homebrew) on a Mac. Thanks!

like image 785
Gbgbgb Avatar asked Jun 17 '16 18:06

Gbgbgb


People also ask

What is keysym in python?

The . keysym column shows the “key symbol”, a string name for the key. This corresponds to the . keysym attribute of the Event object.

What is event keysym?

keysym. A string that is the key's symbolic name (only for keyboard events)

What is event char?

The char event is fired when a character is typed on the keyboard. The char event is different to a key press.


1 Answers

Question 1 and 2 :

I believe your method works, but it seems simpler to just use the str.isprintable method, it returns a boolean indicating whether or not the argument string isprintable:

>>> "aéûβß\\".isprintable() #Works with various accents, special characters
True
>>> "\x16".isprintable()
False
>>> "\n".isprintable() #Excludes all other characters
False

the method using len(repr(event.char) == 3 could work, but it would for instance, also exclude \, which has a repr of 4 chars ('\\').

Question 3 :

Like you said, there are a bunch of gotcha (eg: tab's event char is "\t", returns is "\r"... I don't know where/if you can find an exhaustive list of these particularities, the only solution I ever had was try out the most common (eg pretty much every key and combinason Ctrl+key on my keyboard, using a program of course:

chars = dict()
def LogKey(event, chars = chars): #dict is mutable
    global char #get the actual value of char
    dict[char] = event.char #

root = Tk()
root.bind("<KeyPress>", LogKey)

for character in "abcde...123456...²&é\"'(-è_...)": #and so on
    char = character
    root.event_generate("<KeyPress-{}>".format(character))
    char = "<Control-{}>".format(character)
    root.event_generate(char)
    char = "<Control-Shift-{}>".format(character)
    ...
#Inspect the dictonary chars in the end

It's probably not the best method, but it should cover most cases, you can also expande to test multiple keys (eg Control-c-v)...

like image 131
dlesbre Avatar answered Sep 30 '22 11:09

dlesbre