Multiple Key Event Bindings in Tkinter - "Control + E" "Command (apple) + E" etc

Viewed 14874

Mac OS X 10.6.6 - Tkinter

I want to bind multiple-key events, and while I have found an effbot article and the Tk man pages, I've been unable to make this work correctly. I'm new here.

I've had mixed success. I've been able to get Shift + letter key, but not Control or Command (Apple key). What I really want to do is Command + letter and Control + letter key so it would theoretically work in Windows and OS X.

I want it to work at window-level, so I'm using root. Perhaps there is a better way. Below is what I've tried:

root.bind('<Shift-E>', self.pressedCmdE)   # Works
root.bind('e', self.pressedCmdE)           # Works
root.bind('<Command-E>', self.pressedCmdE) # Does Not Work
#root.bind('<Mod1-E>', self.pressedCmdE)   #   # Do Mod1, M1, and
#root.bind('<M1-E>', self.pressedCmdE)     #   # Command mean the same thing?

Strangely, when I press alt/option + (E, N, or others) it creates an error. Is it interacting with PythonLauncher?

2011-06-16 16:19:22.618 Python[1546:d07] An uncaught exception was raised
2011-06-16 16:19:22.621 Python[1546:d07] *** -[NSCFString characterAtIndex:]: Range or index out of bounds
2011-06-16 16:19:22.622 Python[1546:d07] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSCFString characterAtIndex:]: Range or index out of bounds'
*** Call stack at first throw:
(
    0   CoreFoundation                      0x00007fff85b397b4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x00007fff848b90f3 objc_exception_throw + 45
    2   CoreFoundation                      0x00007fff85b395d7 +[NSException raise:format:arguments:] + 103
    3   CoreFoundation                      0x00007fff85b39564 +[NSException raise:format:] + 148
    4   Foundation                          0x00007fff866eb5e1 -[NSCFString characterAtIndex:] + 97
    5   Tk                                  0x0000000100759bcf Tk_SetCaretPos + 663
    6   Tk                                  0x000000010075fd94 Tk_MacOSXSetupTkNotifier + 699
    7   Tcl                                 0x000000010061d2ae Tcl_DoOneEvent + 297
    8   _tkinter.so                         0x00000001001d9be9 init_tkinter + 1132
    9   Python                              0x0000000100089187 PyEval_EvalFrameEx + 15317
    10  Python                              0x000000010008acce PyEval_EvalCodeEx + 1803
    11  Python                              0x000000010008935e PyEval_EvalFrameEx + 15788
    12  Python                              0x000000010008acce PyEval_EvalCodeEx + 1803
    13  Python                              0x000000010008ad61 PyEval_EvalCode + 54
    14  Python                              0x00000001000a265a Py_CompileString + 78
    15  Python                              0x00000001000a2723 PyRun_FileExFlags + 150
    16  Python                              0x00000001000a423d PyRun_SimpleFileExFlags + 704
    17  Python                              0x00000001000b0286 Py_Main + 2718
    18  Python                              0x0000000100000e6c start + 52
)
terminate called after throwing an instance of 'NSException'
Abort trap
4 Answers

Enhanced to cover the Alt and Meta keys, aka Option and Command on macOS.

# Original <https://StackOverflow.com/questions/6378556/
#           multiple-key-event-bindings-in-tkinter-control-e-command-apple-e-etc>

# Status of alt (ak option), control, meta (aka command)
# and shift keys in Python tkinter

# Note, tested only on macOS 10.13.6 with Python 3.7.4 and Tk 8.6.9

import tkinter as tk
import sys

_macOS = sys.platform == 'darwin'
_Alt   = 'Option' if _macOS else 'Alt'
_Ctrl  = 'Control'
_Meta  = 'Command' if _macOS else 'Meta'
_Shift = 'Shift'

alt = ctrl = meta = shift = ''


def up_down(mod, down):
    print('<%s> %s' % (mod, 'down' if down else 'up'))
    return down


def key(event):
    '''Other key pressed or released'''
    # print(event.keycode, event.keysym, event.down)
    global alt, ctrl, meta, shift
    t = [m for m in (alt, ctrl, shift, meta, str(event.keysym)) if m]
    print('+'.join(t))


def alt_key(down, *unused):
    '''Alt (aka Option on macOS) key is pressed or released'''
    global alt
    alt = up_down(_Alt, down)


def control_key(down, *unused):
    '''Control key is pressed or released'''
    global ctrl
    ctrl = up_down(_Ctrl, down)


def meta_key(down, *unused):
    '''Meta (aka Command on macOS) key is pressed or released'''
    global meta
    meta = up_down(_Meta, down)


def shift_key(down, *unused):
    '''Shift button is pressed or released'''
    global shift
    shift = up_down(_Shift, down)


def modifier(root, mod, handler, down):
    '''Add events and handlers for key press and release'''
    root.event_add('<<%sOn>>' % (mod,), ' <KeyPress-%s_L>' % (mod,), '<KeyPress-%s_R>' % (mod,))
    root.bind(     '<<%sOn>>' % (mod,), lambda _: handler('<%s>' % (down,)))

    root.event_add('<<%sOff>>' % (mod,), '<KeyRelease-%s_L>' % (mod,), '<KeyRelease-%s_R>' % (mod,))
    root.bind(     '<<%sOff>>' % (mod,), lambda _: handler(''))


root = tk.Tk()
root.geometry('256x64+0+0')

modifier(root, 'Alt',     alt_key,     _Alt)
modifier(root, 'Control', control_key, _Ctrl)
modifier(root, 'Meta',    meta_key,    _Meta)
modifier(root, 'Shift',   shift_key,   _Shift)

root.bind('<Key>', key)

root.mainloop()
Related