0

I'm designing a Tkinter GUI that uses Checkbuttons to display the available serial ports on the system. The user will be able to check whichever ports they want to connect to, and should be able to do this either by clicking on the checkboxes they want, or by navigating to each Checkbutton by pressing the Tab key and then pressing the Return key to select/unselect each one they want. I've ran into a bug. Everything works except the Tab navigation. While navigating through the fields of my GUI with the Tab key, once I press Tab to get to COM1 (left) and press enter, it will only toggle COM3 on and off. Then, when I press Tab again to get to COM3 (right), COM3 will still toggle. What I want is for whichever field is highlighted to be bound to the Return key. Using the example below, while COM1 is highlighted, pressing Return turns on/off COM1, instead of COM3

State when COM1 is highlightedState when COM3 is selected

My function takes in a list of values and a frame object, makes Checkbutton for each value in the list, appends the Checkbutton to a list, and returns it. In the instance of my pictures, the list was [COM1, COM3]

def make_checkboxes(frame, checkbox_list):
    """
    Description:            Make a series of Checkboxes for each value in the passed in list
    :param frame:           frame where all checkboxes will be placed
    :param checkbox_list:   list of values that each need a checkbox
    :return:                a checkbox entry list
    """
    boolean_list = []
    check_box = {}
    created_check_boxes = []
    for value in checkbox_list:
        # Create boolean for the current checkbox, set it to off
        boolean = IntVar()
        boolean.set(0)
        # Create checkbox with boolean that toggles it
        check_box = Checkbutton(frame, text=value, variable=boolean)
        check_box.pack()
        check_box.bind('<Return>', lambda e: toggle_check_box(boolean))
        created_check_boxes.append(check_box[value])
    return created_check_boxes

def toggle_check_box(check_box_obj):
    """
    Description:        Toggles a check box on and off when <Return> key is pressed
    :param boolean:     The current state of a checkbox 
    """
    if check_box_obj.get() == 1:
        check_box_obj.set(0)
    elif check_box_obj.get() == 0:
        check_box_obj.set(1)
pjano1
  • 143
  • 1
  • 3
  • 10

1 Answers1

2

There are two problems:

  1. Your code shows the common "lambda-in-a-loop" problem, where the lambda function uses the variable value at execution time, not at definition time, i.e. no matter which button is clicked, boolean will have the value from the last iteration of the loop

  2. What you want to achieve with your binding is already standard behaviour for pressing the Space and Return keys, i.e. with your bind, after fixing the above problem, you will actually toggle the button twice, undoing the effect. You'd only need it if you want to toggle the keys with some other key, e.g. t (for "toggle") (This might depend on the OS, though)

(Also, you probably want check_box[value] = ..., storing the check boxes in the dictionary you create before the loop, but this might just be a typo.)

Putting it all together, you can try this (or just remove the line entirely):

#         index     not space or enter    bind b here          use b here
check_box[value].bind('<t>', lambda e, b=boolean: toggle_check_box(b))
tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • I was unaware of the "lamba-in-a-loop" problem. Replacing the `` with `` did exactly what I needed, and didn't toggle the button twice for some reason. Thank you for breaking this down for me! – pjano1 Feb 26 '20 at 17:04
  • @pjano1 The behaviour might be different depending on the OS. If not Return, does Space work out-of-the-box for you? Just curious. – tobias_k Feb 26 '20 at 17:09
  • yes, when removing the line entirely Space does work for me by default – pjano1 Feb 26 '20 at 18:04