1

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.

1 Answer 1

1

Here's a minimal working example with event bindings for a, Shift+a, Ctrl+a, and Alt+a (naturally these bindings can be modified to use other keys as needed)

import tkinter as tk


class App(tk.Tk):
    def __init__(self) -> None:
        super().__init__()
        self.title('App')
        self.lbl = tk.Label(self, text='Press a, ctrl+a, alt+a, or shift+a')
        self.lbl.pack()

        self.bind('a', lambda e: self.lbl.config(text=e))  # just 'a'
        self.bind('<Control-a>', lambda e: self.lbl.config(text=e))
        self.bind('<Alt-a>', lambda e: self.lbl.config(text=e))
        self.bind('A', lambda e: self.lbl.config(text=e))  # Shift + a


if __name__ == '__main__':
    app = App()
    app.mainloop()

The only one that may be a bit unintuitive is Shift+a, which you'll notice is actually bound to capital A!

Pressing any of these key bindings will show their event info in the Label widget.

You can look at the event's state attribute (i.e., e.state in this example) to see what modifier keys, if any, were held when the binding was triggered.

I've used bind here, but bind_all would also work for this example.


NB: My App class inherits directly from tk.Tk, so I don't have a root here as it isn't necessary

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks. So, according to your explanation, my Test 2.4 and Test 2.6 were working but I needed to check event.state as well. <Alt-a> gives event.keysym=a, event.state=131072, <Control-a> gives event.keysym=a, event.state=4, <a> gives event.keysym=a, event.state=0, <A> gives event.keysym=a, event.state=1, <Shift-a> gives nothing, <Shift-F9> gives event.keysym=F9, event.state=1. Are the status codes platform and OS independent? I want my application to work on any computer and I shouldn't check state 131072 for Alt if it does't work on Linux, for example.
Yep! The state value is the result of the modifier values being "OR"ed together. It might be easier to look at them all as hexadecimal values, i.e. 131072 decimal is 0x20000 hex. So if you see a state value of 0x20000 it means alt was held, 0x00004 if ctrl was held, and so on. Because each modifier key sets a single bit in the state value, they can be combined, e.g. 0x20004 means both alt and ctrl were held, 0x00005 means shift and ctrl were held (4+1), and so on. AFAIK this is less platform dependent than locale/keyboard dependent.
Cool, thanks. Then the only unintuitive thing is, as you say, that we need to use <A> instead of <Shift-a>. But now i know it :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.