I am struggling with some key bindings in Tkinter.
In my application, to avoid having to make each widget get the focus prior to having the opportunity of capturing KeyPress events, I decided to use root.bind_all() to let the root window capture all those events. I want to be able to differentiate between, for example, "a", "Alt+a", "Shift+a" and "Control+a".
Below is a simple program where I have 4 different blocks of code, the tests I have done and the results I have got:
import tkinter as tk
from tkinter import ttk
class App:
def __init__(self, root):
self.root = root
label = ttk.Label(self.root, text="Key binding test", takefocus=True)
label.pack()
# BLOCK 1: to test this block, uncomment it and comment the rest of blocks
self.root.bind_all("<Key>", self.change_color)
# My results:
# Test 1.1: Press 'a' -> 'Event captured: a'
# Test 1.2: Press Shift -> 'Event captured: Shift_L'
# Test 1.3: Press Shift+a -> 'Event captured: Shift_L'
# 'Event captured: A'
# Test 1.4: Press Alt -> no event captured
# Test 1.5: Press Alt+a -> no event captured
# Test 1.6: Press Control -> 'Event captured: Control_L'
# Test 1.7: Press Control+a -> 'Event captured: Control_L'
# 'Event captured: a'
# Test 1.8: Press Alt gr -> 'Event captured: Control_L'
# BLOCK 2: to test this block, uncomment it and comment the rest of blocks
#self.root.event_add("<<KEYS_1>>",
# "<Shift-a>",
# "<Alt-a>",
# "<Control-a>"
# )
#self.root.bind_all("<<KEYS_1>>", self.change_color)
# My results:
# Test 2.1: Press Shift -> no event captured
# Test 2.2: Press Shift+a -> no event captured
# Test 2.3: Press Alt -> no event captured
# Test 2.4: Press Alt+a -> 'Event captured: a'
# Test 2.5: Press Control -> no event captured
# Test 2.6: Press Control+a -> 'Event captured: a'
# BLOCK 3: to test this block, uncomment it and comment the rest of blocks
#self.root.event_add("<<KEYS_2>>",
# "<Shift_L>",
# "<Alt_L>",
# "<Control_L>"
# )
#self.root.bind_all("<<KEYS_2>>", self.change_color)
# My results:
# Test 3.1: Press Shift -> 'Event captured: Shift_L'
# Test 3.2: Press Alt -> no event captured
# Test 3.3: Press Control -> 'Event captured: Control_L'
# BLOCK 4: to test this block, uncomment it and comment the rest of blocks
#self.root.bind_all("<Alt_L>", self.change_color)
#self.root.bind_all("<Shift_L>", self.change_color)
#self.root.bind_all("<Control_L>", self.change_color)
# My results:
# Test 4.1: Press Alt -> 'Event captured: Alt_L'
# Test 4.2: Press Shift -> 'Event captured: Shift_L'
# Test 4.3: Press Control -> 'Event captured: Control_L'
def change_color(self, event):
# Ideally do the change color stuff
print('Event captured: ', event.keysym)
# Also tested with
# return "break"
# and
# return ("break")
# and
# return
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.mainloop()
Test 1 is about capturing any key ("<Key>" binding), Test 2 about Shift, Alt and Control modifiers for Key-a, Test 3 for Shift_L, Alt_L and Control_L key names. Test 4 should be equal to Test 3.
As can be seen,
Test 1.2 captured Shift_L but Test 1.4 didn't capture Alt_L.
Tests 2.1 and 2.2 didn't capture Shift being pressed
Tests 2.4 and 2.6 just printed 'a', meaning I wouldn't know if Alt or Control were used as modifiers first
Test 3.2 didn't capture Alt_L, but Test 4.1 did! And Test 1.4 didn't!
I have tried changing
"<Shift-a>",
"<Alt-a>",
"<Control-a>"
for
"<Shift-KeyPress-a>",
"<Alt-KeyPress-a>",
"<Control-KeyPress-a>"
as suggested by TheLizzard here
"Alt" bindings not working in tkinter text widget
but I got the same results.
Any ideas for this "strange behaviour"? Can it be OS dependant?
I am using Python 3.14 on Windows 11.
I have tested it on a Dell laptop, and also on a HP laptop with external keyboard connected by USB port.
The only way I have found to make it work is to use Symon's approach here:
Binding buttons to Alt keypresses?
defininig his suggested AltOn and AltOff functions, and creating new ShiftOn, ShiftOff, ControlOn and ControlOff ones.