4

I wanted to create line numbers for my text editor, but it's a bit complicated: Tkinter adding line number to text widget

So I decided to create a line and column counter, like in the IDLE. To achieve this goal, I decide to listen for the <Key> event generated on a tkinter.Text widget. I did not know which are the properties of an event object, so I decided to take a look in the source code of the Event class (one of the best tutorials :D), and I discovered there are 2 main properties that are interesting for my goal, that is keycode and char.

My question is, are this keycodes platform independents? I would like that my text editor work on all platforms without undefined behaviour, because of wrong interpretation of keycodes.

This is a simple practical functional example of what I would like to do:

from tkinter import *

lines = 1
columns = 0

ARROWS = (8320768, 8124162, 8255233, 8189699)

def on_key_pressed(event=None):
    global columns, lines

    if event.keycode == 3342463: # return
        if columns > 0 and lines > 0:
            columns -= 1
            if columns < 0: # decrease lines if columns < 0
                columns = 0
                lines -= 1
        if columns == 0 and lines > 1:
            lines -= 1

    elif event.keycode == 2359309: # newline
        columns = 0
        lines += 1
    else:
        if event.keycode not in (ARROWS) and event.char != '':
            columns += 1

    print("Lines =", lines)
    print("Columns =", columns, '\n\n')
    print(event.keycode, ' = ', repr(event.char))

root = Tk()

text = Text(root)

text.pack(fill='both', expand=1)
text.bind('<Key>', on_key_pressed)

root.mainloop()

What are the problems of this approach (after answering my first question)?

Community
  • 1
  • 1
nbro
  • 15,395
  • 32
  • 113
  • 196
  • 1
    There's already a problem with this approach, for example when I select a part of the text and then I delete it, the lines and columns do not update... – nbro Dec 16 '14 at 21:20
  • On Windows 10 Pro I get: {'keysym': 'Left', 'keysym_num': 65361, 'keycode': 37} {'keysym': 'Up', 'keysym_num': 65362, 'keycode': 38} {'keysym': 'Down', 'keysym_num': 65364, 'keycode': 40} {'keysym': 'Right', 'keysym_num': 65363, 'keycode': 39} So yes they very from OS to OS. – spacether Dec 15 '20 at 18:04
  • On MacOs 10.14.16 I get: {'keysym': 'Left', 'keysym_num': 65361, 'keycode': 8124162} {'keysym': 'Up', 'keysym_num': 65362, 'keycode': 8320768} {'keysym': 'Down', 'keysym_num': 65364, 'keycode': 8255233} {'keysym': 'Right', 'keysym_num': 65363, 'keycode': 8189699} – spacether Dec 15 '20 at 18:10

3 Answers3

4

On my Logitech keyboard with win7, the arrow keycodes are 37, 38, 39, and 40, with repr '', and return is 13, with repr '\r'. These keycode do not match either what you show, or the claims of this page.

However, the same page suggests using event.keysym or event.keysym_num. What it does not point out is that the nums for non-unicode-character keys, like Shift, Up, F1, etc, are just below 2**16 (65536), in the 'private use area'. The number for ascii chars are their ascii codes, which equal their unicode ordinals. I suspect that the number for all BMP unicode chars is their unicode ordinal (tk only encodes the BMP).

The only use I can see for keycodes is to tell the difference between number keypad keys and the same key elsewhere.

Terry Jan Reedy
  • 18,414
  • 3
  • 40
  • 52
1

The problems with your approach is that it will be almost impossible to compute the information you seek. All the information you need is available by querying widget attributes and/or data so there's really no point in trying to keep track of the line and column yourself.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
0

This example is not an elegant solution, so I think it is better to "hardcode" keycodes depending on the operating system.

# 1. Generate keystrokes using Python + Tkinter.
# 2. Save needed keycodes in the dictionary.
# 3. Use dictionary with keycodes to handle keystrokes independently from
#    operating system, language or (maybe?) keyboard model.
# This is not an elegant solution, so I think it is better to "hardcode"
# keycodes depending on the operating system.
import tkinter as tk

def keystroke(event):
    dict[event.keycode] = event.keysym  # save keycodes into the dictionary

def keyboardevent(str):
    # Code that simulated 'key' being pressed on keyboard
    temp.after(10, lambda: temp.event_generate('<Key-{}>'.format(str)))

temp = tk.Tk()
temp.withdraw()  # remove the window from the screen (without destroying it)
temp.bind('<Key>', keystroke)
dict = {}  # dictionary of the needed keycodes
keyboardevent('w')  # generate needed keyboard events
keyboardevent('s')
keyboardevent('a')
keyboardevent('d')
temp.after(20, temp.destroy)  # this is not needed anymore
temp.mainloop()

# Start your code here
def keys_handler(event):
    if event.keycode in dict:
        print(dict[event.keycode])

root = tk.Tk()
root.focus_force()
root.bind('<Key>', keys_handler)
root.mainloop()
FooBar167
  • 2,721
  • 1
  • 26
  • 37