Python Series Part 19: Binding Keys and Events - Part 1

Jarret B

Well-Known Member
Staff member
Joined
May 22, 2017
Messages
454
Reaction score
519
Credits
19,511
In the article on Tkinter Buttons, I mentioned binding keys to allow a key press to activate a button. Of course, other widgets have this ability as well.

For buttons, we use an ‘underline’ feature to underline a character to show a user can access it with a key press instead of clicking it with the mouse.

We can use the key binding process more later when we get to the article on ‘menus’.

We will also cover a lengthy list of Events that can occur, because binding keys is creating an event that occurs when a user presses a key. There are other events to go over.

Basics of Key Binding

To begin with, there are four event variables that we can get information from to determine what key(s) were pressed:
  1. event.keysym
  2. event.keycode
  3. event.keysym_num
  4. event.state
The ‘keysym’ is used to show the symbol of the key pressed. For example, if you press the ‘b’ button on the keyboard, the ‘keysym’ will be ‘b’ or ‘B’ depending if you have ‘CAPS LOCK’ on. The ‘keysym’ will not change if you also press the ALT or CTRL key.

Do remember that when pushing a modifier key, such as ALT, CTRL or SHIFT, that key press registers as the modifier key, then when the other key is pressed, such as ‘b’, it will then register as ‘keysym’ and the button ‘b’.

The ‘keycode’ is a numeric value for a key. The code is the same whether a letter is lower- or uppercase. For example, ‘B’ and ‘b’ both have a keycode of 56. In a bit, I will give you the coding to print out the code of a key that you press. You’ll be able to see that the keycodes are determined by the location of the keys on the keyboard. For example, ‘b’ is 56 and ‘n’ is 57. The ‘keycode’ value does not change when a modifier key is pressed as well.

NOTE: The results given by the bindings we discuss are based on an English (US) keyboard layout. For foreign keyboards, the keycodes may be different.

The ‘keysym_num’ is a numeric value similar to ‘keycode’, but will also vary depending on the modifier key being pressed as well. For ‘b’, the ‘keysym_num’ is 98. For SHIFT and ‘b’, the number is 66. When pressing ‘b’ with ALT or CTRL, the ‘keycode_num’ is 98 since it is still lower-case ‘b’. If I use SHIFT and ALT with ‘b’, then it is 66 since it is uppercase ‘B’. So, the ‘keysym_num’ is based on the upper- or lower-cased character.

The last event value is ‘state’. This is a generic holder for the modifier button. For my system, the modifier keys are:
  1. ALT - 24
  2. CTRL - 20
  3. SHIFT - 17
Other keys that have no screen output also have their own values. For example, ‘CAPS LOCK’ has a value of 16 when turning on CAPS LOCK and 18 when turning it off. These numbers change when using another modifier key.

We can bind individual keys or all keys. If we bind individual keys, we need a function for each key binding or a single function to handle them all. For binding multiple keys, we can use a single function to handle the binding.

So, let’s look at binding a single key. Let’s assume we want to capture the ‘b’ key. We can use:

Code:
root.bind("b", pressed_b)

When you press the ‘b’ button, the function ‘pressed_b’ is executed. You can also use ‘labmda’ here as well:

Code:
root.bind("b", lambda event: print("You pressed lower-case b"))

If you want to catch the uppercase ‘B’ as well, then you need another line like:

Code:
root.bind("B", lambda event: print("You pressed upper-case B"))

You can see that capturing individual keys can be cumbersome if you have a quite a few shortcut keys in use.

So, we can capture all the keys and then handle the keys after that. To catch all key presses:

Code:
root.bind('<Key>', key_check)

You can name the function whatever you wish as long as it is a valid function name.

So, how do we handle it then? Let’s look at an example of the keys ‘b’ and ‘B’:

Code:
def key_check(e):
c = e.keysym
s = e.state
if (c=='b' or c=='B'):

We can then set up the code to handle what happens when the button is pressed.

For most systems, we only use shortcuts when the ALT key is pressed with an underlined character. Let’s look at this one:

Code:
from tkinter import *
root = Tk()
root.title('Button Underline')
root.geometry("400x800")

def key_check(e):
   k = e.keysym
   s = e.state
   if (k=='b' and s==24) or (k=='b' and s==27) or (k=='B' and s==25) or (k=='B' and s==26):
       b1.invoke()

for row in range(0,9):
    root.grid_rowconfigure(row, weight=1)

for col in range(0,3):
    root.grid_columnconfigure(col, weight=1)

def clicked():
   l1['text']="You pressed me!"

b1=Button(text="Button 1",underline=0,command=clicked)
l1=Label(text=" ")
b1.grid(column=1, row=0)
l1.grid(column=1, row=3)

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

root.mainloop()

Now, I bound all keys (<Key>) to the function ‘key_check’. The function gets the ‘event.keysym’ value and places it in the variable ‘k’. It then gets the 'event.state’ value and places it in the variable ‘s’.

We then come across an ‘if’ statement that checks four possibilities:
  1. (k=='b' and s==24) - lower-case ‘b’ and the ALT (24) key
  2. (k=='b' and s==27) - lower-case ‘b’ and the CAPS LOCK (on), SHIFT, ALT (27) keys
  3. (k=='B' and s==25) - upper-case ‘B’ and SHIFT, ALT (25) keys
  4. (k=='B' and s==26) - upper-case ‘B’ and CAPS LOCK (on), ALT (26) keys
As you can see, the ‘state’ value can change as other modifier keys are turned on. You may not want to worry about all of them, but some users may find things frustrating if it doesn’t work just because the CAPS LOCK key is on.

You can simplify the line with the code:

Code:
if (k=='b' or k=='B') and (s==24 or s==25 or s==26 or s==27):

If one of these is true, then it invokes the button click of ‘b1’ which then calls the function ‘clicked’.

NOTE: You can do a binding to ‘<Alt-B>’ and ‘<Alt-b>’ which works. You can use any character and even change the modifier to ‘Alt’, ‘Shift’ or ‘Control’.

Now for something a little more interesting. There are two ALT buttons, both left and right. The same is true for SHIFT and CTRL. So, let’s capture the ALT keys and perform a function. We can also perform a function when the keys are released. In this example, I will use the ‘underline’ option when either ALT is pressed and then undo the ‘underline’ option when either ALT key is released:

Code:
from tkinter import *

root = Tk()
root.title('Button Underline')
root.geometry("400x800")

def key_check(e):
    k = e.keysym
    s = e.state
    if (k=='b' or k=='B') and (s==24 or s==25 or s==26 or s==27):
        altoff_check
        b1.invoke()

def alt_check(e):
    b1.configure(underline=0)

def altoff_check(e):
   b1.configure(underline=-1)

for row in range(0,9):
    root.grid_rowconfigure(row, weight=1)

for col in range(0,3):
    root.grid_columnconfigure(col, weight=1)

def clicked():
    b1.flash()

l1['text']="You pressed me!"
b1=Button(text="Button 1",command=clicked)
l1=Label(text=" ")
b1.grid(column=1, row=0)
l1.grid(column=1, row=3)

root.bind('<Key>', key_check)
root.bind('<Alt_L>', alt_check)
root.bind('<Alt_R>', alt_check)
root.bind('<KeyRelease-Alt_L>',altoff_check)
root.bind('<KeyRelease-Alt_R>',altoff_check)

root.mainloop()

You should notice that even though we are capturing all keys, the individual keys we are binding still work. If a key affects more than one binding, then the last binding option is used.

We turn on the underline when either ALT key is pressed and then turn it off when either key is released or when ALT+B is pressed. I also threw in the line to ‘flash’ the button as well, no matter how the button is activated.

More Key Events

We have a few keyboard events to cover to hopefully get them all. Some are not keyboard events exactly. They will be triggered by key presses, but deal more with widgets. For example, when a widget gets focus, we can perform a function.

I will try to get an example for each one so it will be easier to understand by seeing code. Also, you can copy and paste the code onto your IDE and run it to see how it works.

Let’s divide these up a bit to make it a little easier. There are three basic event types:

1. Keyboard
2. Mouse
3. Window

Each of these either occurs for a keyboard, mouse or by a window. We’ve already covered a bit about binding keys, but we can do more. It is possible to determine mouse activity and perform actions because of them. The last is triggered events occurring because of the window we are using, which can also be used on objects in the window.

Keyboard: KeyPress and KeyRelease

Instead of binding keys as we have done, which really is handling when a key is pressed, we can detect when a key is pressed or even released. By checking a key that has been released, we can capture the modifiers a little better since both keys are released.

Code:
from tkinter import *

def button_1():
    print("Button 1")

def button_2():
    print("Button 2")

def on_release(event):
    c = event.keysym
    if c=='1':
        print ("Key 1 Released")
        b1.invoke()
    if c=='2':
        print ("Key 2 Released")
        b2.invoke()

def on_press(event):
    c = event.keysym
    if c=='1':
        print ("Key 1 Pressed")
    if c=='2':
        print ("Key 2 Pressed")

root = Tk()
root.title('Binding KeyPress and KeyRelease')
root.geometry("400x800")

for row in range(0,9):
    root.grid_rowconfigure(row, weight=1)
for col in range(0,3):
    root.grid_columnconfigure(col, weight=1)

b1 = Button(root,text="Button 1",underline=7,command=button_1)
b2 = Button(root,text="Button 2",underline=7,command=button_2)
b1.grid(column=0, row=0)
b2.grid(column=0, row=3)
root.bind('<KeyRelease>', on_release)
root.bind('<KeyPress>', on_press)

root.mainloop()

Here, we have two buttons, ‘Button 1’ and ‘Button 2’. If we press either button with the mouse, we get a line printed to the terminal telling us which button was pressed. We also have bound ‘KeyRelease’ and ‘KeyPress’. When we press or release any key on the keyboard, we call a separate function. These two functions get the ‘keysym’ so we can check if the shortcut keys, ‘1’ or ‘2’, were pressed. We the check which key was pressed or released. We print out a line to specify which event and key was triggered. After this, we ‘invoke’ the button and get another line for that button.

Mouse : ButtonPress and ButtonRelease

We can tell when a keyboard key is pressed or released, so we can do the same for the mouse.

Let’s set up a code that will have a label that will be filled with info on which button was pressed:

Code:
from tkinter import *

def on_release(event):
if event.num==1:
     l1.configure(text="Left Button Released")
if event.num==2:
    l1.configure(text="Middle Button Released")
if event.num==3:
    l1.configure(text="Right Button Released")

def on_press(event):
    if event.num==1:
        l1.configure(text="Left Button Pressed")
    if event.num==2:
        l1.configure(text="Middle Button Pressed")
   if event.num==3:
        l1.configure(text="Right Button Pressed")

root = Tk()
root.title('Binding ButtonPress and ButtonRelease')
root.geometry("400x800")

for row in range(0,9):
    root.grid_rowconfigure(row, weight=1)
for col in range(0,3):
    root.grid_columnconfigure(col, weight=1)

l1 = Label(text=' ')
l1.grid(column=0, row=0)
root.bind('<ButtonRelease>', on_release)
root.bind('<ButtonPress>', on_press)

root.mainloop()

Here, we bind the mouse keys with ‘ButtonRelease’ and ‘ButtonPress’. Once the event is triggered and the function called, we can determine which button was used. We can check the value of ‘event.num’ to see if it is key 1, 2 or 3.

We can bind specific buttons for being pressed with ‘ButtonPress-1’, ‘ButtonPress-2’ and ‘ButtonPress-3’. The same is for releasing a button, ‘ButtonRelease-1’, ‘ButtonRelease-2’ and ‘ButtonRelease-3’.

Mouse : Motion

Maybe we need to know where the mouse is at in the window. We can do this with the following example:

Code:
from tkinter import *

def motion(event):
    x, y = event.x, event.y
    l1.configure(text=('{}, {}'.format(x, y)))

root = Tk()
root.title('Binding Motion')
root.geometry("400x800")

l1=Label(text=' ' )
l1.grid(column=0, row=0)
root.bind('<Motion>', motion)

root.mainloop()

This is a basic example of using the ‘motion’ event. The top left corner of the window is ‘0,0’ and the bottom right is ‘399,799’ as set by the geometry. The values are one less since the numbering starts at zero.

NOTE: Keep in mind that you can bind these events to objects and not just the window.

Mouse : Enter and Leave

We can test for the mouse cursor to enter or leave an object. The object can be the whole window, such as 'root', or a button, label,etc.

Let's go over a basic example of entering and leaving the main window, or what we will call 'root'. When the mouse enters the window, the background of the window changes to blue. If the mouse cursor leaves the window, then it changes to red. Initially, the mouse may or may not be in the window, so the default color is used until the cursor is in the window.

Code:
from tkinter import *

root = Tk()
root.title('Mouse Enter and Leave')
root.geometry("400x800")

def on_enter(event):
    root.configure(bg="blue")
def on_leave(event):
    root.configure(bg="red")

for row in range(0,9):
    root.grid_rowconfigure(row, weight=1)
for col in range(0,3):
    root.grid_columnconfigure(col, weight=1)

root.bind("<Enter>", on_enter)
root.bind("<Leave>", on_leave)

root.mainloop()

So we can bind the 'Enter' event and the 'Leave' event. We can also bind them to another object:

Code:
b1.bind("<Enter>", on_b1_enter)
b1.bind("<Leave>", on_b1_leave)

Each object can be bound and used in the same program, but if you leave an object, such as a button or label, it triggers the event for 'on_leave' for the root. We can check if the 'event.widget' is 'root' and only allows the root to be changed when the root is entered or left. Child objects of the root will cause an improper event to occur:

Code:
from tkinter import *

root = Tk()
root.title('Mouse Enter and Leave 2')
root.geometry("400x800")

def on_enter(event):
    if event.widget is root:
        root.configure(bg="blue")

def on_leave(event):
    if event.widget is root:
        root.configure(bg="red")

def l1_on_enter(event):
    l1.configure(bg="magenta")

def l1_on_leave(event):
     l1.configure(bg="green")

for row in range(0,9):
     root.grid_rowconfigure(row, weight=1)
for col in range(0,3):
    root.grid_columnconfigure(col, weight=1)

root.bind("<Enter>", on_enter)
root.bind("<Leave>", on_leave)
l1 = Label(text="Label 1")
l1.grid(column=1, row=0)
l1.bind("<Enter>", l1_on_enter)
l1.bind("<Leave>", l1_on_leave)

root.mainloop()

So, as you can see, we only change the root background color if the root widget is the object being entered or exited. The same may occur if a child object has another child object within it.

Conclusion

This is only the first part of this article and there is quite a bit covered and yet to be covered.

Be sure you have a good grasp on these examples and we can continue with Bindings and Events.
 


Follow Linux.org

Staff online

Members online


Top